2
0
mirror of https://gitlab.com/apparmor/apparmor synced 2025-08-22 18:17:09 +00:00
apparmor/utils/test/test-conditional.py
2025-08-13 12:31:52 -03:00

403 lines
22 KiB
Python

#!/usr/bin/python3
# ----------------------------------------------------------------------
# Copyright (C) 2025 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 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 apparmor.common import AppArmorException, AppArmorBug, combine_profname
from apparmor.profile_storage import ProfileStorage
from apparmor.profile_list import ProfileList
from apparmor.rule.file import FileRule
from apparmor.rule.variable import VariableRule
from apparmor.rule.boolean import BooleanRule
from apparmor.rule.conditional import ConditionalBlock, AppArmorAst, Term, CompareCondition, ConditionalRule, BooleanCondition
from common_test import AATest, setup_all_loops
class TestConditional(AATest):
""" Base class to test conditionals, profile_data does not contain any rules """
filename = 'somefile'
condition_contents = '\n'
tests = (
# ConditionalBlock clean rule
(['if $FOO {', '} else if $BAR {', '} else {'], 'if $FOO {' + condition_contents + '} else if $BAR {' + condition_contents + '} else {' + condition_contents + '}'),
(['if not $FOO {', '} else if not $BAR {', '} else {'], 'if not $FOO {' + condition_contents + '} else if not $BAR {' + condition_contents + '} else {' + condition_contents + '}'),
(['if defined $FOO {', '} else if not defined $BAR {', '} else {'], 'if defined $FOO {' + condition_contents + '} else if not defined $BAR {' + condition_contents + '} else {' + condition_contents + '}'),
(['if not defined @{VAR2} {', '} else if defined @{VAR1} {', '} else {'], 'if not defined @{VAR2} {' + condition_contents + '} else if defined @{VAR1} {' + condition_contents + '} else {' + condition_contents + '}'),
(['if not defined @{VAR2} {', '} else {'], 'if not defined @{VAR2} {' + condition_contents + '} else {' + condition_contents + '}'),
(['if not defined @{VAR2} {'], 'if not defined @{VAR2} {' + condition_contents + '}'),
)
@classmethod
def setUpClass(cls):
super(TestConditional, cls).setUpClass()
cls.active_profiles = ProfileList()
cls.profile_data = {}
cls.condition_contents = '\n'
(profile, hat, prof_storage) = ProfileStorage.parse('profile foo {', cls.filename, 0, False, False)
cls.profile_data[profile] = prof_storage
cls.active_profiles.add_rule(cls.filename, 'variable', VariableRule.create_instance('@{VAR1} = "test"'))
cls.active_profiles.add_rule(cls.filename, 'variable', VariableRule.create_instance('@{VAR2} = "test1 test2"'))
cls.active_profiles.add_rule(cls.filename, 'variable', VariableRule.create_instance('@{VAR3} = 10'))
cls.active_profiles.add_rule(cls.filename, 'boolean', BooleanRule.create_instance('$FOO=true'))
cls.active_profiles.add_rule(cls.filename, 'boolean', BooleanRule.create_instance('$BAR=false'))
def _run_test(self, params, expected):
conditional_block = None
for condition in params:
if condition == params[0]:
conditional_block = ConditionalBlock(condition, self.active_profiles.files[self.filename])
conditional_block.store_profile_data(self.profile_data)
else:
conditional_block.add_conditional(condition, self.active_profiles.files[self.filename])
conditional_block.store_profile_data(self.profile_data)
self.assertEqual(conditional_block.get_clean(), expected)
class TestConditionalComplex(TestConditional):
""" Class to test complex conditionals (and, ors, comparisons and boolean operations), profile_data contains one file rule """
# directly related to how setUpClass sets up profile_data
condition_contents = '\n /bin/false rix,\n\n'
tests = (
# ConditionalBlock clean rule
(['if $FOO and ($BAR) {', '} else if $BAR {', '} else {'], 'if $FOO and $BAR {' + condition_contents + '} else if $BAR {' + condition_contents + '} else {' + condition_contents + '}'),
(['if $FOO and ($BAR or defined @{VAR1}) {', '} else if $BAR {', '} else {'], 'if $FOO and ($BAR or defined @{VAR1}) {' + condition_contents + '} else if $BAR {' + condition_contents + '} else {' + condition_contents + '}'),
(['if $FOO or ($BAR or defined @{VAR1}) {', '} else if $BAR {', '} else {'], 'if $FOO or $BAR or defined @{VAR1} {' + condition_contents + '} else if $BAR {' + condition_contents + '} else {' + condition_contents + '}'),
(['if $FOO or ($BAR and defined @{VAR1}) {', '} else if $BAR {', '} else {'], 'if $FOO or $BAR and defined @{VAR1} {' + condition_contents + '} else if $BAR {' + condition_contents + '} else {' + condition_contents + '}'),
(['if not $FOO {', '} else if not not $BAR {', '} else {'], 'if not $FOO {' + condition_contents + '} else if $BAR {' + condition_contents + '} else {' + condition_contents + '}'),
(['if ((test in @{VAR1})) and defined $FOO {'], 'if test in @{VAR1} and defined $FOO {' + condition_contents + '}'),
(['if test3 in @{VAR2} {'], 'if test3 in @{VAR2} {' + condition_contents + '}'),
(['if 9 < @{VAR3} {'], 'if 9 < @{VAR3} {' + condition_contents + '}'),
(['if test in "test2 test" {'], 'if test in "test2 test" {' + condition_contents + '}'),
)
@classmethod
def setUpClass(cls):
super(TestConditionalComplex, cls).setUpClass()
(profile, hat, prof_storage) = ProfileStorage.parse('profile foo {', cls.filename, 0, False, False)
cls.profile_data[profile] = prof_storage
cls.profile_data[profile]['file'].add(FileRule.create_instance('/bin/false rix,'))
class TestConditionalSimple(TestConditional):
""" Class to test simple conditionals (boolean and defined operations), profile_data contains one file rule """
# directly related to how setUpClass sets up profile_data
condition_contents = '\n /bin/false rix,\n\n'
tests = (
# ConditionalBlock clean rule
(['if $FOO {', '} else if $BAR {', '} else {'], 'if $FOO {' + condition_contents + '} else if $BAR {' + condition_contents + '} else {' + condition_contents + '}'),
(['if not $FOO {', '} else if not $BAR {', '} else {'], 'if not $FOO {' + condition_contents + '} else if not $BAR {' + condition_contents + '} else {' + condition_contents + '}'),
(['if defined $FOO {', '} else if not defined ${BAR} {', '} else {'], 'if defined $FOO {' + condition_contents + '} else if not defined ${BAR} {' + condition_contents + '} else {' + condition_contents + '}'),
(['if not defined @{VAR2} {', '} else if defined @{VAR1} {', '} else {'], 'if not defined @{VAR2} {' + condition_contents + '} else if defined @{VAR1} {' + condition_contents + '} else {' + condition_contents + '}'),
(['if not defined @{VAR2} {', '} else {'], 'if not defined @{VAR2} {' + condition_contents + '} else {' + condition_contents + '}'),
(['if not defined @{VAR2} {'], 'if not defined @{VAR2} {' + condition_contents + '}'),
)
@classmethod
def setUpClass(cls):
super(TestConditionalSimple, cls).setUpClass()
(profile, hat, prof_storage) = ProfileStorage.parse('profile foo {', cls.filename, 0, False, False)
cls.profile_data[profile] = prof_storage
cls.profile_data[profile]['file'].add(FileRule.create_instance('/bin/false rix,'))
class TestConditionalSubProfile(TestConditional):
""" Class to test simple conditionals, profile_data contains hat definition """
# directly related to how setUpClass sets up profile_data
condition_contents = '\n /** w,\n /bin/false rix,\n\n ^bar {\n /bin/true rix,\n\n }\n'
tests = (
# ConditionalBlock clean rule
(['if $FOO {', '} else if $BAR {', '} else {'], 'if $FOO {' + condition_contents + '} else if $BAR {' + condition_contents + '} else {' + condition_contents + '}'),
(['if not $FOO {', '} else if not $BAR {', '} else {'], 'if not $FOO {' + condition_contents + '} else if not $BAR {' + condition_contents + '} else {' + condition_contents + '}'),
(['if defined $FOO {', '} else if not defined ${BAR} {', '} else {'], 'if defined $FOO {' + condition_contents + '} else if not defined ${BAR} {' + condition_contents + '} else {' + condition_contents + '}'),
(['if not defined @{VAR2} {', '} else if defined @{VAR1} {', '} else {'], 'if not defined @{VAR2} {' + condition_contents + '} else if defined @{VAR1} {' + condition_contents + '} else {' + condition_contents + '}'),
(['if not defined @{VAR2} {', '} else {'], 'if not defined @{VAR2} {' + condition_contents + '} else {' + condition_contents + '}'),
(['if not defined @{VAR2} {'], 'if not defined @{VAR2} {' + condition_contents + '}'),
)
@classmethod
def setUpClass(cls):
super(TestConditionalSubProfile, cls).setUpClass()
(profile, hat, prof_storage) = ProfileStorage.parse('profile foo {', cls.filename, 0, False, False)
cls.profile_data[profile] = prof_storage
cls.profile_data[profile]['file'].add(FileRule.create_instance('/bin/false rix,'))
cls.profile_data[profile]['file'].add(FileRule.create_instance('/** w,'))
(profile, hat, prof_storage) = ProfileStorage.parse('^bar {', cls.filename, 0, profile, None)
profname = combine_profname((profile, hat))
cls.profile_data[profname] = prof_storage
cls.profile_data[profname]['in_cond'] = True
cls.profile_data[profname]['file'].add(FileRule.create_instance('/bin/true rix,'))
class TestConditionalLogprof(TestConditional):
""" Class to test output of Logprof """
# directly related to how setUpClass sets up profile_data
condition_contents = '\n /** w,\n /bin/false rix,\n\n ^bar {\n /bin/true rix,\n\n }\n'
tests = (
(['if $FOO {', '} else if $BAR {', '} else {'],
['ConditionalBlock', [['Conditional', 'if $FOO {' + condition_contents + '}'],
['Conditional', 'else if $BAR {' + condition_contents + '}'],
['Conditional', 'else {' + condition_contents + '}']]]),
(['if not $FOO {', '} else if not $BAR {', '} else {'],
['ConditionalBlock', [['Conditional', 'if not $FOO {' + condition_contents + '}'],
['Conditional', 'else if not $BAR {' + condition_contents + '}'],
['Conditional', 'else {' + condition_contents + '}']]]),
(['if defined $FOO {', '} else if not defined $BAR {', '} else {'],
['ConditionalBlock', [['Conditional', 'if defined $FOO {' + condition_contents + '}'],
['Conditional', 'else if not defined $BAR {' + condition_contents + '}'],
['Conditional', 'else {' + condition_contents + '}']]]),
(['if not defined @{VAR2} {', '} else if defined @{VAR1} {', '} else {'],
['ConditionalBlock', [['Conditional', 'if not defined @{VAR2} {' + condition_contents + '}'],
['Conditional', 'else if defined @{VAR1} {' + condition_contents + '}'],
['Conditional', 'else {' + condition_contents + '}']]]),
(['if not defined @{VAR2} {', '} else {'],
['ConditionalBlock', [['Conditional', 'if not defined @{VAR2} {' + condition_contents + '}'],
['Conditional', 'else {' + condition_contents + '}']]]),
(['if not defined @{VAR2} {'],
['ConditionalBlock', [['Conditional', 'if not defined @{VAR2} {' + condition_contents + '}']]]),
)
@classmethod
def setUpClass(cls):
super(TestConditionalLogprof, cls).setUpClass()
(profile, hat, prof_storage) = ProfileStorage.parse('profile foo {', cls.filename, 0, False, False)
cls.profile_data[profile] = prof_storage
cls.profile_data[profile]['file'].add(FileRule.create_instance('/bin/false rix,'))
cls.profile_data[profile]['file'].add(FileRule.create_instance('/** w,'))
(profile, hat, prof_storage) = ProfileStorage.parse('^bar {', cls.filename, 0, profile, None)
profname = combine_profname((profile, hat))
cls.profile_data[profname] = prof_storage
cls.profile_data[profname]['in_cond'] = True
cls.profile_data[profname]['file'].add(FileRule.create_instance('/bin/true rix,'))
def _run_test(self, params, expected):
conditional_block = None
for condition in params:
if condition == params[0]:
conditional_block = ConditionalBlock(condition, self.active_profiles.files[self.filename])
conditional_block.store_profile_data(self.profile_data)
else:
conditional_block.add_conditional(condition, self.active_profiles.files[self.filename])
conditional_block.store_profile_data(self.profile_data)
self.assertEqual(conditional_block.logprof_header(), expected)
class TestConditionalEquality(AATest):
""" Class to test equality tests """
filename = 'somefile'
profile_data = {}
tests = (
# should always be different
# first condition second condition
((['if not $FOO {'], profile_data), (['if $FOO {'], profile_data)),
((['if defined $FOO {'], profile_data), (['if $FOO {'], profile_data)),
((['if $BAR {'], profile_data), (['if $FOO {'], profile_data)),
((['if $FOO {'], None), (['if $FOO {'], profile_data)),
((['if $FOO {', '} else if $BAR {'], profile_data), (['if $FOO {', '} else {'], profile_data)),
((['if $FOO and $BAR {'], profile_data), (['if $FOO and $BAR and defined $BAZ{'], profile_data)),
)
@classmethod
def setUpClass(cls):
super(TestConditionalEquality, cls).setUpClass()
cls.active_profiles = ProfileList()
(profile, hat, prof_storage) = ProfileStorage.parse('profile foo {', cls.filename, 0, False, False)
cls.profile_data[profile] = prof_storage
cls.profile_data[profile]['file'].add(FileRule.create_instance('/bin/false rix,'))
cls.profile_data[profile]['file'].add(FileRule.create_instance('/** w,'))
(profile, hat, prof_storage) = ProfileStorage.parse('^bar {', cls.filename, 0, profile, None)
profname = combine_profname((profile, hat))
cls.profile_data[profname] = prof_storage
cls.profile_data[profname]['in_cond'] = True
cls.profile_data[profname]['file'].add(FileRule.create_instance('/bin/true rix,'))
cls.active_profiles.add_rule(cls.filename, 'variable', VariableRule.create_instance('@{VAR1} = "test"'))
cls.active_profiles.add_rule(cls.filename, 'boolean', BooleanRule.create_instance('$FOO=true'))
cls.active_profiles.add_rule(cls.filename, 'boolean', BooleanRule.create_instance('$BAR=false'))
def init_cond(self, conds, profile_data):
block = None
for cond in conds:
if cond == conds[0]:
block = ConditionalBlock(cond, self.active_profiles.files[self.filename])
block.store_profile_data(profile_data)
else:
block.add_conditional(cond, self.active_profiles.files[self.filename])
block.store_profile_data(profile_data)
return block
def _run_test(self, cond1, cond2):
conditional_block1 = self.init_cond(cond1[0], cond1[1])
conditional_block2 = self.init_cond(cond2[0], cond2[1])
self.assertFalse(conditional_block1.is_equal(conditional_block2))
self.assertFalse(conditional_block1.is_covered(conditional_block2))
def test_is_equal_condition(self):
conditional_block1 = self.init_cond(['if $FOO {', '} else if $BAR {', '} else {'], self.profile_data)
conditional_block2 = self.init_cond(['if $FOO {', '} else {'], self.profile_data)
self.assertFalse(conditional_block1.is_equal(conditional_block2))
self.assertFalse(conditional_block1.is_covered(conditional_block2))
def test_is_equal(self):
conditional_block1 = ConditionalBlock('if not defined $FOO {', self.active_profiles.files[self.filename])
conditional_block1.store_profile_data(self.profile_data)
self.assertTrue(conditional_block1.is_equal(conditional_block1))
self.assertTrue(conditional_block1.is_covered(conditional_block1))
class TestConditionalException(AATest):
filename = 'somefile'
profile_data = {}
tests = (
(['if @{VAR1} {', '} else if $BAR {', '} else {'], AppArmorException), # set variable used in boolean comparison
(['if $FOO {', '} else if @{VAR} {', '} else {'], AppArmorException), # undefined variable in else if
(['if ${BAZ} {', '} else {'], AppArmorException), # undefined boolean variable with else
(['if ${VAR} {'], AppArmorException), # undefined boolean variable
(['if foo in @{UNDERFINED} {'], AppArmorException), # undefined set variable
)
@classmethod
def setUpClass(cls):
super(TestConditionalException, cls).setUpClass()
cls.active_profiles = ProfileList()
cls.condition_contents = '\n'
(profile, hat, prof_storage) = ProfileStorage.parse('profile foo {', cls.filename, 0, False, False)
cls.profile_data[profile] = prof_storage
cls.active_profiles.add_rule(cls.filename, 'variable', VariableRule.create_instance('@{VAR1} = "test"'))
cls.active_profiles.add_rule(cls.filename, 'boolean', BooleanRule.create_instance('$FOO=true'))
cls.active_profiles.add_rule(cls.filename, 'boolean', BooleanRule.create_instance('$BAR=false'))
def _run_test(self, params, expected):
conditional_block = None
with self.assertRaises(expected):
for condition in params:
if condition == params[0]:
conditional_block = ConditionalBlock(condition, self.active_profiles.files[self.filename])
conditional_block.store_profile_data(self.profile_data)
else:
conditional_block.add_conditional(condition, self.active_profiles.files[self.filename])
conditional_block.store_profile_data(self.profile_data)
def test_create_instance_block1(self):
with self.assertRaises(NotImplementedError):
ConditionalBlock.create_instance('test')
def test_create_instance_block2(self):
with self.assertRaises(NotImplementedError):
ConditionalBlock._create_instance('test', None)
class TestAppArmorAstException(AATest):
tests = (
('foo = "1"', AppArmorBug), # AppArmorAst only supports the actual condition
('~ $FOO', AppArmorBug), # Invalid unary operator
('func(${BAZ})', AppArmorBug), # Invalid function name
)
def _run_test(self, params, expected):
ast = AppArmorAst(params)
with self.assertRaises(expected):
ast.get_clean()
with self.assertRaises(expected):
ast.evaluate(None)
class TestMiscClassException(AATest):
def test_invalid_op_1(self):
term = Term.create_instance('10')
op = 'is'
with self.assertRaises(AppArmorBug):
CompareCondition(term, op, term)
def test_invalid_op_2(self):
term = Term.create_instance('10')
cond = CompareCondition(term, 'in', term)
cond.op = 'is'
with self.assertRaises(AppArmorBug):
cond.evaluate(None)
def test_invalid_term(self):
with self.assertRaises(AppArmorBug):
Term.create_instance('#')
def test_invalid_conditional_condition(self):
with self.assertRaises(AppArmorBug):
ConditionalRule('invalid', AppArmorAst('defined $BAR'))
def test_invalid_conditional_ast_tree(self):
with self.assertRaises(AppArmorBug):
ConditionalRule(ConditionalRule.IF, 'defined $BAR')
def test_invalid_conditional_clean(self):
cond = ConditionalRule(ConditionalRule.IF, AppArmorAst('defined $BAR'))
cond.condition = 'invalid'
with self.assertRaises(AppArmorBug):
cond.get_clean()
def test_invalid_boolean_defined(self):
with self.assertRaises(AppArmorBug):
BooleanCondition(None, Term.create_instance('10'))
def test_invalid_boolean_varible(self):
with self.assertRaises(AppArmorBug):
BooleanCondition('defined', '10')
def test_invalid_compare_left(self):
with self.assertRaises(AppArmorBug):
CompareCondition(None, '==', Term.create_instance('10'))
def test_invalid_compare_right(self):
with self.assertRaises(AppArmorBug):
CompareCondition(Term.create_instance('10'), '==', None)
def test_invalid_compare_op(self):
with self.assertRaises(AppArmorBug):
CompareCondition(Term.create_instance('10'), '~', Term.create_instance('10'))
def test_invalid_compare(self):
term = Term.create_instance('10')
cond = CompareCondition(term, '==', term)
with self.assertRaises(AppArmorBug):
cond.compare(cond.op, term.get_set(None), 10)
def test_invalid_compare_evaluate(self):
term = InvalidTerm(10)
cond = CompareCondition(term, '==', term)
with self.assertRaises(AppArmorBug):
cond.evaluate(None)
class InvalidTerm(Term):
def __init__(self, value):
self.value = value
def get_set(self, prof_storage):
return self.value # not a set
setup_all_loops(__name__)
if __name__ == '__main__':
unittest.main(verbosity=1)