apparmor/utils/test/test-change_profile.py
Christian Boltz 70f9334cd9 Add tests for ChangeProfileRule and ChangeProfileRuleset
As usual, those classes have 100% test coverage.


Acked-by: Steve Beattie <steve@nxnw.org>
2015-05-28 22:23:32 +02:00

443 lines
22 KiB
Python

#!/usr/bin/env python
# ----------------------------------------------------------------------
# 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 unittest
from collections import namedtuple
from common_test import AATest, setup_all_loops
from apparmor.rule.change_profile import ChangeProfileRule, ChangeProfileRuleset
from apparmor.rule import BaseRule
from apparmor.common import AppArmorException, AppArmorBug
from apparmor.logparser import ReadLog
exp = namedtuple('exp', ['audit', 'allow_keyword', 'deny', 'comment',
'execcond', 'all_execconds', 'targetprofile', 'all_targetprofiles'])
# --- tests for single ChangeProfileRule --- #
class ChangeProfileTest(AATest):
def _compare_obj(self, obj, expected):
self.assertEqual(expected.allow_keyword, obj.allow_keyword)
self.assertEqual(expected.audit, obj.audit)
self.assertEqual(expected.execcond, obj.execcond)
self.assertEqual(expected.targetprofile, obj.targetprofile)
self.assertEqual(expected.all_execconds, obj.all_execconds)
self.assertEqual(expected.all_targetprofiles, obj.all_targetprofiles)
self.assertEqual(expected.deny, obj.deny)
self.assertEqual(expected.comment, obj.comment)
class ChangeProfileTestParse(ChangeProfileTest):
tests = [
# rawrule audit allow deny comment execcond all? targetprof all?
('change_profile,' , exp(False, False, False, '' , None , True , None , True )),
('change_profile /foo,' , exp(False, False, False, '' , '/foo', False, None , True )),
('change_profile /foo -> /bar,' , exp(False, False, False, '' , '/foo', False, '/bar' , False)),
('deny change_profile /foo -> /bar, # comment' , exp(False, False, True , ' # comment' , '/foo', False, '/bar' , False)),
('audit allow change_profile /foo,' , exp(True , True , False, '' , '/foo', False, None , True )),
('change_profile -> /bar,' , exp(False, False, False, '' , None , True , '/bar' , False)),
('audit allow change_profile -> /bar,' , exp(True , True , False, '' , None , True , '/bar' , False)),
# quoted versions
('change_profile "/foo",' , exp(False, False, False, '' , '/foo', False, None , True )),
('change_profile "/foo" -> "/bar",' , exp(False, False, False, '' , '/foo', False, '/bar' , False)),
('deny change_profile "/foo" -> "/bar", # cmt' , exp(False, False, True, ' # cmt' , '/foo', False, '/bar' , False)),
('audit allow change_profile "/foo",' , exp(True , True , False, '' , '/foo', False, None , True )),
('change_profile -> "/bar",' , exp(False, False, False, '' , None , True , '/bar' , False)),
('audit allow change_profile -> "/bar",' , exp(True , True , False, '' , None , True , '/bar' , False)),
# with globbing and/or named profiles
('change_profile,' , exp(False, False, False, '' , None , True , None , True )),
('change_profile /*,' , exp(False, False, False, '' , '/*' , False, None , True )),
('change_profile /* -> bar,' , exp(False, False, False, '' , '/*' , False, 'bar' , False)),
('deny change_profile /** -> bar, # comment' , exp(False, False, True , ' # comment' , '/**' , False, 'bar' , False)),
('audit allow change_profile /**,' , exp(True , True , False, '' , '/**' , False, None , True )),
('change_profile -> "ba r",' , exp(False, False, False, '' , None , True , 'ba r' , False)),
('audit allow change_profile -> "ba r",' , exp(True , True , False, '' , None , True , 'ba r' , False)),
]
def _run_test(self, rawrule, expected):
self.assertTrue(ChangeProfileRule.match(rawrule))
obj = ChangeProfileRule.parse(rawrule)
self.assertEqual(rawrule.strip(), obj.raw_rule)
self._compare_obj(obj, expected)
class ChangeProfileTestParseInvalid(ChangeProfileTest):
tests = [
('change_profile -> ,' , AppArmorException),
('change_profile foo -> ,' , AppArmorException),
]
def _run_test(self, rawrule, expected):
self.assertFalse(ChangeProfileRule.match(rawrule))
with self.assertRaises(expected):
ChangeProfileRule.parse(rawrule)
class ChangeProfileTestParseFromLog(ChangeProfileTest):
def test_net_from_log(self):
parser = ReadLog('', '', '', '', '')
event = 'type=AVC msg=audit(1428699242.551:386): apparmor="DENIED" operation="change_profile" profile="/foo/changeprofile" pid=3459 comm="changeprofile" target="/foo/rename"'
# libapparmor doesn't understand this log format (from JJ)
# event = '[ 97.492562] audit: type=1400 audit(1431116353.523:77): apparmor="DENIED" operation="change_profile" profile="/foo/changeprofile" pid=3459 comm="changeprofile" target="/foo/rename"'
parsed_event = parser.parse_event(event)
self.assertEqual(parsed_event, {
'request_mask': None,
'denied_mask': None,
'error_code': 0,
#'family': 'inet',
'magic_token': 0,
'parent': 0,
'profile': '/foo/changeprofile',
'operation': 'change_profile',
'resource': None,
'info': None,
'aamode': 'REJECTING',
'time': 1428699242,
'active_hat': None,
'pid': 3459,
'task': 0,
'attr': None,
'name2': '/foo/rename', # target
'name': None,
})
obj = ChangeProfileRule(ChangeProfileRule.ALL, parsed_event['name2'], log_event=parsed_event)
# audit allow deny comment execcond all? targetprof all?
expected = exp(False, False, False, '' , None, True, '/foo/rename', False)
self._compare_obj(obj, expected)
self.assertEqual(obj.get_raw(1), ' change_profile -> /foo/rename,')
class ChangeProfileFromInit(ChangeProfileTest):
tests = [
# ChangeProfileRule object audit allow deny comment execcond all? targetprof all?
(ChangeProfileRule('/foo', '/bar', deny=True) , exp(False, False, True , '' , '/foo', False, '/bar' , False)),
(ChangeProfileRule('/foo', '/bar') , exp(False, False, False, '' , '/foo', False, '/bar' , False)),
(ChangeProfileRule('/foo', ChangeProfileRule.ALL) , exp(False, False, False, '' , '/foo', False, None , True )),
(ChangeProfileRule(ChangeProfileRule.ALL, '/bar') , exp(False, False, False, '' , None , True , '/bar' , False)),
(ChangeProfileRule(ChangeProfileRule.ALL,
ChangeProfileRule.ALL) , exp(False, False, False, '' , None , True , None , True )),
]
def _run_test(self, obj, expected):
self._compare_obj(obj, expected)
class InvalidChangeProfileInit(AATest):
tests = [
# init params expected exception
(['/foo', '' ] , AppArmorBug), # empty targetprofile
(['' , '/bar' ] , AppArmorBug), # empty execcond
([' ', '/bar' ] , AppArmorBug), # whitespace execcond
(['/foo', ' ' ] , AppArmorBug), # whitespace targetprofile
(['xyxy', '/bar' ] , AppArmorException), # invalid execcond
([dict(), '/bar' ] , AppArmorBug), # wrong type for execcond
([None , '/bar' ] , AppArmorBug), # wrong type for execcond
(['/foo', dict() ] , AppArmorBug), # wrong type for targetprofile
(['/foo', None ] , AppArmorBug), # wrong type for targetprofile
]
def _run_test(self, params, expected):
with self.assertRaises(expected):
ChangeProfileRule(params[0], params[1])
def test_missing_params_1(self):
with self.assertRaises(TypeError):
ChangeProfileRule()
def test_missing_params_2(self):
with self.assertRaises(TypeError):
ChangeProfileRule('inet')
class InvalidChangeProfileTest(AATest):
def _check_invalid_rawrule(self, rawrule):
obj = None
self.assertFalse(ChangeProfileRule.match(rawrule))
with self.assertRaises(AppArmorException):
obj = ChangeProfileRule(ChangeProfileRule.parse(rawrule))
self.assertIsNone(obj, 'ChangeProfileRule handed back an object unexpectedly')
def test_invalid_net_missing_comma(self):
self._check_invalid_rawrule('change_profile') # missing comma
def test_invalid_net_non_ChangeProfileRule(self):
self._check_invalid_rawrule('dbus,') # not a change_profile rule
def test_empty_net_data_1(self):
obj = ChangeProfileRule('/foo', '/bar')
obj.execcond = ''
# no execcond set, and ALL not set
with self.assertRaises(AppArmorBug):
obj.get_clean(1)
def test_empty_net_data_2(self):
obj = ChangeProfileRule('/foo', '/bar')
obj.targetprofile = ''
# no targetprofile set, and ALL not set
with self.assertRaises(AppArmorBug):
obj.get_clean(1)
class WriteChangeProfileTestAATest(AATest):
tests = [
# raw rule clean rule
(' change_profile , # foo ' , 'change_profile, # foo'),
(' audit change_profile /foo,' , 'audit change_profile /foo,'),
(' deny change_profile /foo -> bar,# foo bar' , 'deny change_profile /foo -> bar, # foo bar'),
(' deny change_profile /foo ,# foo bar' , 'deny change_profile /foo, # foo bar'),
(' allow change_profile -> /bar ,# foo bar' , 'allow change_profile -> /bar, # foo bar'),
(' allow change_profile /** -> /bar ,# foo bar' , 'allow change_profile /** -> /bar, # foo bar'),
(' allow change_profile "/fo o" -> "/b ar",' , 'allow change_profile "/fo o" -> "/b ar",'),
]
def _run_test(self, rawrule, expected):
self.assertTrue(ChangeProfileRule.match(rawrule))
obj = ChangeProfileRule.parse(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')
def test_write_manually(self):
obj = ChangeProfileRule('/foo', 'bar', allow_keyword=True)
expected = ' allow change_profile /foo -> bar,'
self.assertEqual(expected, obj.get_clean(2), 'unexpected clean rule')
self.assertEqual(expected, obj.get_raw(2), 'unexpected raw rule')
class ChangeProfileCoveredTest(AATest):
def _run_test(self, param, expected):
obj = ChangeProfileRule.parse(self.rule)
check_obj = ChangeProfileRule.parse(param)
self.assertTrue(ChangeProfileRule.match(param))
self.assertEqual(obj.is_equal(check_obj), expected[0], 'Mismatch in is_equal, expected %s' % expected[0])
self.assertEqual(obj.is_equal(check_obj, True), expected[1], 'Mismatch in is_equal/strict, expected %s' % expected[1])
self.assertEqual(obj.is_covered(check_obj), expected[2], 'Mismatch in is_covered, expected %s' % expected[2])
self.assertEqual(obj.is_covered(check_obj, True, True), expected[3], 'Mismatch in is_covered/exact, expected %s' % expected[3])
class ChangeProfileCoveredTest_01(ChangeProfileCoveredTest):
rule = 'change_profile /foo,'
tests = [
# rule equal strict equal covered covered exact
(' change_profile,' , [ False , False , False , False ]),
(' change_profile /foo,' , [ True , True , True , True ]),
(' change_profile /foo, # comment', [ True , False , True , True ]),
(' allow change_profile /foo,' , [ True , False , True , True ]),
(' change_profile /foo,' , [ True , False , True , True ]),
(' change_profile /foo -> /bar,' , [ False , False , True , True ]),
(' change_profile /foo -> bar,' , [ False , False , True , True ]),
('audit change_profile /foo,' , [ False , False , False , False ]),
('audit change_profile,' , [ False , False , False , False ]),
(' change_profile /asdf,' , [ False , False , False , False ]),
(' change_profile -> /bar,' , [ False , False , False , False ]),
('audit deny change_profile /foo,' , [ False , False , False , False ]),
(' deny change_profile /foo,' , [ False , False , False , False ]),
]
class ChangeProfileCoveredTest_02(ChangeProfileCoveredTest):
rule = 'audit change_profile /foo,'
tests = [
# rule equal strict equal covered covered exact
( 'change_profile /foo,' , [ False , False , True , False ]),
('audit change_profile /foo,' , [ True , True , True , True ]),
( 'change_profile /foo -> /bar,' , [ False , False , True , False ]),
('audit change_profile /foo -> /bar,' , [ False , False , True , True ]), # XXX is "covered exact" correct here?
( 'change_profile,' , [ False , False , False , False ]),
('audit change_profile,' , [ False , False , False , False ]),
(' change_profile -> /bar,' , [ False , False , False , False ]),
]
class ChangeProfileCoveredTest_03(ChangeProfileCoveredTest):
rule = 'change_profile /foo -> /bar,'
tests = [
# rule equal strict equal covered covered exact
( 'change_profile /foo -> /bar,' , [ True , True , True , True ]),
('allow change_profile /foo -> /bar,' , [ True , False , True , True ]),
( 'change_profile /foo,' , [ False , False , False , False ]),
( 'change_profile,' , [ False , False , False , False ]),
( 'change_profile /foo -> /xyz,' , [ False , False , False , False ]),
('audit change_profile,' , [ False , False , False , False ]),
('audit change_profile /foo -> /bar,' , [ False , False , False , False ]),
( 'change_profile -> /bar,' , [ False , False , False , False ]),
( 'change_profile,' , [ False , False , False , False ]),
]
class ChangeProfileCoveredTest_04(ChangeProfileCoveredTest):
rule = 'change_profile,'
tests = [
# rule equal strict equal covered covered exact
( 'change_profile,' , [ True , True , True , True ]),
('allow change_profile,' , [ True , False , True , True ]),
( 'change_profile /foo,' , [ False , False , True , True ]),
( 'change_profile /xyz -> bar,' , [ False , False , True , True ]),
( 'change_profile -> /bar,' , [ False , False , True , True ]),
( 'change_profile /foo -> /bar,' , [ False , False , True , True ]),
('audit change_profile,' , [ False , False , False , False ]),
('deny change_profile,' , [ False , False , False , False ]),
]
class ChangeProfileCoveredTest_05(ChangeProfileCoveredTest):
rule = 'deny change_profile /foo,'
tests = [
# rule equal strict equal covered covered exact
( 'deny change_profile /foo,' , [ True , True , True , True ]),
('audit deny change_profile /foo,' , [ False , False , False , False ]),
( 'change_profile /foo,' , [ False , False , False , False ]), # XXX should covered be true here?
( 'deny change_profile /bar,' , [ False , False , False , False ]),
( 'deny change_profile,' , [ False , False , False , False ]),
]
class ChangeProfileCoveredTest_Invalid(AATest):
def test_borked_obj_is_covered_1(self):
obj = ChangeProfileRule.parse('change_profile /foo,')
testobj = ChangeProfileRule('/foo', '/bar')
testobj.execcond = ''
with self.assertRaises(AppArmorBug):
obj.is_covered(testobj)
def test_borked_obj_is_covered_2(self):
obj = ChangeProfileRule.parse('change_profile /foo,')
testobj = ChangeProfileRule('/foo', '/bar')
testobj.targetprofile = ''
with self.assertRaises(AppArmorBug):
obj.is_covered(testobj)
def test_invalid_is_covered(self):
obj = ChangeProfileRule.parse('change_profile /foo,')
testobj = BaseRule() # different type
with self.assertRaises(AppArmorBug):
obj.is_covered(testobj)
def test_invalid_is_equal(self):
obj = ChangeProfileRule.parse('change_profile -> /bar,')
testobj = BaseRule() # different type
with self.assertRaises(AppArmorBug):
obj.is_equal(testobj)
# --- tests for ChangeProfileRuleset --- #
class ChangeProfileRulesTest(AATest):
def test_empty_ruleset(self):
ruleset = ChangeProfileRuleset()
ruleset_2 = ChangeProfileRuleset()
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))
def test_ruleset_1(self):
ruleset = ChangeProfileRuleset()
rules = [
'change_profile -> /bar,',
'change_profile /foo,',
]
expected_raw = [
'change_profile -> /bar,',
'change_profile /foo,',
'',
]
expected_clean = [
'change_profile -> /bar,',
'change_profile /foo,',
'',
]
for rule in rules:
ruleset.add(ChangeProfileRule.parse(rule))
self.assertEqual(expected_raw, ruleset.get_raw())
self.assertEqual(expected_clean, ruleset.get_clean())
def test_ruleset_2(self):
ruleset = ChangeProfileRuleset()
rules = [
'change_profile /foo -> /bar,',
'allow change_profile /asdf,',
'deny change_profile -> xy, # example comment',
]
expected_raw = [
' change_profile /foo -> /bar,',
' allow change_profile /asdf,',
' deny change_profile -> xy, # example comment',
'',
]
expected_clean = [
' deny change_profile -> xy, # example comment',
'',
' allow change_profile /asdf,',
' change_profile /foo -> /bar,',
'',
]
for rule in rules:
ruleset.add(ChangeProfileRule.parse(rule))
self.assertEqual(expected_raw, ruleset.get_raw(1))
self.assertEqual(expected_clean, ruleset.get_clean(1))
class ChangeProfileGlobTestAATest(AATest):
def setUp(self):
self.ruleset = ChangeProfileRuleset()
def test_glob_1(self):
self.assertEqual(self.ruleset.get_glob('change_profile /foo,'), 'change_profile,')
# not supported or used yet, glob behaviour not decided yet
# def test_glob_2(self):
# self.assertEqual(self.ruleset.get_glob('change_profile /foo -> /bar,'), 'change_profile -> /bar,')
def test_glob_ext(self):
with self.assertRaises(AppArmorBug):
# get_glob_ext is not available for change_profile rules
self.ruleset.get_glob_ext('change_profile /foo -> /bar,')
class ChangeProfileDeleteTestAATest(AATest):
pass
setup_all_loops(__name__)
if __name__ == '__main__':
unittest.main(verbosity=2)