From eae9ff97d5396daea57dd9fc4bdbfb0fea7bcae5 Mon Sep 17 00:00:00 2001 From: Chris PeBenito Date: Thu, 17 Dec 2015 10:38:04 -0500 Subject: [PATCH] setools: add PolicyDifference class Begin diff functions by implementing type difference function. Closes #32 --- setools/__init__.py | 3 + setools/diff.py | 183 ++++++++++++++++++++++++++++++++++++++++++ tests/__init__.py | 1 + tests/diff.py | 138 +++++++++++++++++++++++++++++++ tests/diff_left.conf | 127 +++++++++++++++++++++++++++++ tests/diff_right.conf | 127 +++++++++++++++++++++++++++++ 6 files changed, 579 insertions(+) create mode 100644 setools/diff.py create mode 100644 tests/diff.py create mode 100644 tests/diff_left.conf create mode 100644 tests/diff_right.conf diff --git a/setools/__init__.py b/setools/__init__.py index 1108706..5e936e8 100644 --- a/setools/__init__.py +++ b/setools/__init__.py @@ -65,3 +65,6 @@ from .permmap import PermissionMap # Domain Transition Analysis from .dta import DomainTransitionAnalysis + +# Policy difference +from .diff import PolicyDifference diff --git a/setools/diff.py b/setools/diff.py new file mode 100644 index 0000000..3022b74 --- /dev/null +++ b/setools/diff.py @@ -0,0 +1,183 @@ +# Copyright 2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 2.1 of +# the License, or (at your option) any later version. +# +# SETools 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with SETools. If not, see +# . +# +import logging +from collections import namedtuple +from weakref import WeakKeyDictionary + +__all__ = ['PolicyDifference'] + + +modified_types_record = namedtuple("modified_type", ["added_attributes", + "removed_attributes", + "matched_attributes", + "modified_permissive", + "permissive", + "added_aliases", + "removed_aliases", + "matched_aliases"]) + + +class DiffResultDescriptor(object): + + """Descriptor for managing diff results.""" + + # @properties could be used instead, but there are so + # many result attributes, this will keep the code more compact. + + def __init__(self, diff_function): + self.diff_function = diff_function + + # use weak references so instances can be + # garbage collected, rather than unnecessarily + # kept around due to this descriptor. + self.instances = WeakKeyDictionary() + + def __get__(self, obj, objtype=None): + if obj is None: + return self + + if self.instances.setdefault(obj, None) is None: + diff = getattr(obj, self.diff_function) + diff() + + return self.instances[obj] + + def __set__(self, obj, value): + self.instances[obj] = value + + +class PolicyDifference(object): + + """ + Determine the differences between two policies. + + All results are represented as str rather than the + original Python objects. This was done because the + source of the object becomes less clear (Python + set logic doesn't have any guarantees for set + intersection). Using str will prevent problems + if you expect to be using a symbol but it is + coming from the wrong policy. + """ + + def __init__(self, left_policy, right_policy): + self.log = logging.getLogger(self.__class__.__name__) + self.left_policy = left_policy + self.right_policy = right_policy + + # + # Policies to compare + # + @property + def left_policy(self): + return self._left_policy + + @left_policy.setter + def left_policy(self, policy): + self._left_policy = policy + self._reset_diff() + + @property + def right_policy(self): + return self._right_policy + + @right_policy.setter + def right_policy(self, policy): + self._right_policy = policy + self._reset_diff() + + # + # Type differences + # + added_types = DiffResultDescriptor("diff_types") + removed_types = DiffResultDescriptor("diff_types") + modified_types = DiffResultDescriptor("diff_types") + + def diff_types(self): + """Generate the difference in types between the policies.""" + + self.log.info( + "Generating type differences from {0.left_policy} to {0.right_policy}".format(self)) + + self.added_types, self.removed_types, matched_types = self._set_diff( + self.left_policy.types(), self.right_policy.types()) + + self.modified_types = dict() + + for name in matched_types: + # Criteria for modified types + # 1. change to attribute set, or + # 2. change to alias set, or + # 3. different permissive setting + left_type = self.left_policy.lookup_type(name) + right_type = self.right_policy.lookup_type(name) + + added_attr, removed_attr, matched_attr = self._set_diff(left_type.attributes(), + right_type.attributes()) + + added_aliases, removed_aliases, matched_aliases = self._set_diff(left_type.aliases(), + right_type.aliases()) + + left_permissive = left_type.ispermissive + right_permissive = right_type.ispermissive + mod_permissive = left_permissive != right_permissive + + if added_attr or removed_attr or added_aliases or removed_aliases or mod_permissive: + self.modified_types[name] = modified_types_record(added_attr, + removed_attr, + matched_attr, + mod_permissive, + left_permissive, + added_aliases, + removed_aliases, + matched_aliases) + + # + # Internal functions + # + def _reset_diff(self): + """Reset diff results on policy changes.""" + self.added_types = None + self.removed_types = None + self.modified_types = None + + @staticmethod + def _set_diff(left, right): + """ + Standard diff of two sets. + + Parameters: + left An iterable + right An iterable + + Return: + tuple (added, removed, matched) + + added Set of items in right but not left + removed Set of items in left but not right + matched Set of items in both left and right + """ + + left_items = set(str(l) for l in left) + right_items = set(str(r) for r in right) + added_items = right_items - left_items + removed_items = left_items - right_items + matched_items = left_items & right_items + + return added_items, removed_items, matched_items diff --git a/tests/__init__.py b/tests/__init__.py index b210e02..75bffe7 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -19,6 +19,7 @@ from . import boolquery from . import categoryquery from . import constraintquery from . import commonquery +from . import diff from . import dta from . import fsusequery from . import genfsconquery diff --git a/tests/diff.py b/tests/diff.py new file mode 100644 index 0000000..711d35f --- /dev/null +++ b/tests/diff.py @@ -0,0 +1,138 @@ +# Copyright 2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# SETools 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. +# +# You should have received a copy of the GNU General Public License +# along with SETools. If not, see . +# +import unittest + +from setools import SELinuxPolicy, PolicyDifference + + +class PolicyDifferenceTest(unittest.TestCase): + + """Policy difference tests.""" + + def setUp(self): + self.diff = PolicyDifference(SELinuxPolicy("tests/diff_left.conf"), + SELinuxPolicy("tests/diff_right.conf")) + + def test_001_added_types(self): + """Diff: added type""" + self.assertSetEqual(set(["added_type"]), self.diff.added_types) + + def test_002_removed_types(self): + """Diff: modified type""" + self.assertSetEqual(set(["removed_type"]), self.diff.removed_types) + + def test_003_modified_types_count(self): + """Diff: total modified types""" + self.assertEqual(6, len(self.diff.modified_types)) + + def test_004_modified_types_remove_attr(self): + """Diff: modified type with removed attribute.""" + self.assertIn("modified_remove_attr", self.diff.modified_types) + removed_attrs = self.diff.modified_types["modified_remove_attr"].removed_attributes + self.assertSetEqual(set(["an_attr"]), removed_attrs) + self.assertFalse(self.diff.modified_types["modified_remove_attr"].added_attributes) + self.assertFalse(self.diff.modified_types["modified_remove_attr"].matched_attributes) + self.assertFalse(self.diff.modified_types["modified_remove_attr"].modified_permissive) + self.assertFalse(self.diff.modified_types["modified_remove_attr"].permissive) + self.assertFalse(self.diff.modified_types["modified_remove_attr"].added_aliases) + self.assertFalse(self.diff.modified_types["modified_remove_attr"].removed_aliases) + self.assertFalse(self.diff.modified_types["modified_remove_attr"].matched_aliases) + + def test_005_modified_types_remove_alias(self): + """Diff: modified type with removed alias.""" + self.assertIn("modified_remove_alias", self.diff.modified_types) + removed_alias = self.diff.modified_types["modified_remove_alias"].removed_aliases + self.assertSetEqual(set(["an_alias"]), removed_alias) + self.assertFalse(self.diff.modified_types["modified_remove_alias"].added_attributes) + self.assertFalse(self.diff.modified_types["modified_remove_alias"].removed_attributes) + self.assertFalse(self.diff.modified_types["modified_remove_alias"].matched_attributes) + self.assertFalse(self.diff.modified_types["modified_remove_alias"].modified_permissive) + self.assertFalse(self.diff.modified_types["modified_remove_alias"].permissive) + self.assertFalse(self.diff.modified_types["modified_remove_alias"].added_aliases) + self.assertFalse(self.diff.modified_types["modified_remove_alias"].matched_aliases) + + def test_006_modified_types_remove_permissive(self): + """Diff: modified type with removed permissve.""" + self.assertIn("modified_remove_permissive", self.diff.modified_types) + self.assertFalse(self.diff.modified_types["modified_remove_permissive"].added_attributes) + self.assertFalse(self.diff.modified_types["modified_remove_permissive"].removed_attributes) + self.assertFalse(self.diff.modified_types["modified_remove_permissive"].matched_attributes) + self.assertTrue(self.diff.modified_types["modified_remove_permissive"].modified_permissive) + self.assertTrue(self.diff.modified_types["modified_remove_permissive"].permissive) + self.assertFalse(self.diff.modified_types["modified_remove_permissive"].added_aliases) + self.assertFalse(self.diff.modified_types["modified_remove_permissive"].removed_aliases) + self.assertFalse(self.diff.modified_types["modified_remove_permissive"].matched_aliases) + + def test_007_modified_types_add_attr(self): + """Diff: modified type with added attribute.""" + self.assertIn("modified_add_attr", self.diff.modified_types) + added_attrs = self.diff.modified_types["modified_add_attr"].added_attributes + self.assertSetEqual(set(["an_attr"]), added_attrs) + self.assertFalse(self.diff.modified_types["modified_add_attr"].removed_attributes) + self.assertFalse(self.diff.modified_types["modified_add_attr"].matched_attributes) + self.assertFalse(self.diff.modified_types["modified_add_attr"].modified_permissive) + self.assertFalse(self.diff.modified_types["modified_add_attr"].permissive) + self.assertFalse(self.diff.modified_types["modified_add_attr"].added_aliases) + self.assertFalse(self.diff.modified_types["modified_add_attr"].removed_aliases) + self.assertFalse(self.diff.modified_types["modified_add_attr"].matched_aliases) + + def test_008_modified_types_add_alias(self): + """Diff: modified type with added alias.""" + self.assertIn("modified_add_alias", self.diff.modified_types) + added_alias = self.diff.modified_types["modified_add_alias"].added_aliases + self.assertSetEqual(set(["an_alias"]), added_alias) + self.assertFalse(self.diff.modified_types["modified_add_alias"].added_attributes) + self.assertFalse(self.diff.modified_types["modified_add_alias"].removed_attributes) + self.assertFalse(self.diff.modified_types["modified_add_alias"].matched_attributes) + self.assertFalse(self.diff.modified_types["modified_add_alias"].modified_permissive) + self.assertFalse(self.diff.modified_types["modified_add_alias"].permissive) + self.assertFalse(self.diff.modified_types["modified_add_alias"].removed_aliases) + self.assertFalse(self.diff.modified_types["modified_add_alias"].matched_aliases) + + def test_009_modified_types_add_permissive(self): + """Diff: modified type with added permissive.""" + self.assertIn("modified_add_permissive", self.diff.modified_types) + self.assertFalse(self.diff.modified_types["modified_add_permissive"].added_attributes) + self.assertFalse(self.diff.modified_types["modified_add_permissive"].removed_attributes) + self.assertFalse(self.diff.modified_types["modified_add_permissive"].matched_attributes) + self.assertTrue(self.diff.modified_types["modified_add_permissive"].modified_permissive) + self.assertFalse(self.diff.modified_types["modified_add_permissive"].permissive) + self.assertFalse(self.diff.modified_types["modified_add_permissive"].added_aliases) + self.assertFalse(self.diff.modified_types["modified_add_permissive"].removed_aliases) + self.assertFalse(self.diff.modified_types["modified_add_permissive"].matched_aliases) + + +class PolicyDifferenceTestNoDiff(unittest.TestCase): + + """Policy difference test with no policy differences.""" + + def setUp(self): + self.diff = PolicyDifference(SELinuxPolicy("tests/diff_left.conf"), + SELinuxPolicy("tests/diff_left.conf")) + + def test_001_added_types(self): + """NoDiff: no added types""" + self.assertFalse(self.diff.added_types) + + def test_002_removed_types(self): + """NoDiff: no removed types""" + self.assertFalse(self.diff.removed_types) + + def test_003_modified_types(self): + """NoDiff: no modified types""" + self.assertFalse(self.diff.modified_types) diff --git a/tests/diff_left.conf b/tests/diff_left.conf new file mode 100644 index 0000000..cbd7d65 --- /dev/null +++ b/tests/diff_left.conf @@ -0,0 +1,127 @@ +class infoflow +class infoflow2 +class infoflow3 +class infoflow4 +class infoflow5 +class infoflow6 +class infoflow7 + +sid kernel +sid security + +common infoflow +{ + low_w + med_w + hi_w + low_r + med_r + hi_r +} + +class infoflow +inherits infoflow + +class infoflow2 +inherits infoflow +{ + super_w + super_r +} + +class infoflow3 +{ + null +} + +class infoflow4 +inherits infoflow + +class infoflow5 +inherits infoflow + +class infoflow6 +inherits infoflow + +class infoflow7 +inherits infoflow +{ + super_w + super_r + super_none + super_both + super_unmapped +} + +sensitivity low_s; +sensitivity medium_s alias med; +sensitivity high_s; + +dominance { low_s med high_s } + +category here; +category there; +category elsewhere alias lost; + +#level decl +level low_s:here.there; +level med:here, elsewhere; +level high_s:here.lost; + +#some constraints +mlsconstrain infoflow hi_r ((l1 dom l2) or (t1 == mls_exempt)); + +attribute mls_exempt; +attribute an_attr; + +type system; +role system; +role system types system; + +################################################################################ +# Type enforcement declarations and rules + +type removed_type; + +type modified_remove_attr, an_attr; + +type modified_remove_alias alias an_alias; + +type modified_remove_permissive; +permissive modified_remove_permissive; + +type modified_add_attr; + +type modified_add_alias; + +type modified_add_permissive; + +################################################################################ + +#users +user system roles system level med range low_s - high_s:here.lost; + +#normal constraints +constrain infoflow hi_w (u1 == u2); + +#isids +sid kernel system:system:system:medium_s:here +sid security system:system:system:high_s:lost + +#fs_use +fs_use_trans devpts system:object_r:system:low_s; +fs_use_xattr ext3 system:object_r:system:low_s; +fs_use_task pipefs system:object_r:system:low_s; + +#genfscon +genfscon proc / system:object_r:system:med +genfscon proc /sys system:object_r:system:low_s +genfscon selinuxfs / system:object_r:system:high_s:here.there + +portcon tcp 80 system:object_r:system:low_s + +netifcon eth0 system:object_r:system:low_s system:object_r:system:low_s + +nodecon 127.0.0.1 255.255.255.255 system:object_r:system:low_s:here +nodecon ::1 ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff system:object_r:system:low_s:here + diff --git a/tests/diff_right.conf b/tests/diff_right.conf new file mode 100644 index 0000000..f647bc5 --- /dev/null +++ b/tests/diff_right.conf @@ -0,0 +1,127 @@ +class infoflow +class infoflow2 +class infoflow3 +class infoflow4 +class infoflow5 +class infoflow6 +class infoflow7 + +sid kernel +sid security + +common infoflow +{ + low_w + med_w + hi_w + low_r + med_r + hi_r +} + +class infoflow +inherits infoflow + +class infoflow2 +inherits infoflow +{ + super_w + super_r +} + +class infoflow3 +{ + null +} + +class infoflow4 +inherits infoflow + +class infoflow5 +inherits infoflow + +class infoflow6 +inherits infoflow + +class infoflow7 +inherits infoflow +{ + super_w + super_r + super_none + super_both + super_unmapped +} + +sensitivity low_s; +sensitivity medium_s alias med; +sensitivity high_s; + +dominance { low_s med high_s } + +category here; +category there; +category elsewhere alias lost; + +#level decl +level low_s:here.there; +level med:here, elsewhere; +level high_s:here.lost; + +#some constraints +mlsconstrain infoflow hi_r ((l1 dom l2) or (t1 == mls_exempt)); + +attribute mls_exempt; +attribute an_attr; + +type system; +role system; +role system types system; + +################################################################################ +# Type enforcement declarations and rules + +type added_type; + +type modified_remove_attr; + +type modified_remove_alias; + +type modified_remove_permissive; + +type modified_add_attr, an_attr; + +type modified_add_alias alias an_alias; + +type modified_add_permissive; +permissive modified_add_permissive; + +################################################################################ + +#users +user system roles system level med range low_s - high_s:here.lost; + +#normal constraints +constrain infoflow hi_w (u1 == u2); + +#isids +sid kernel system:system:system:medium_s:here +sid security system:system:system:high_s:lost + +#fs_use +fs_use_trans devpts system:object_r:system:low_s; +fs_use_xattr ext3 system:object_r:system:low_s; +fs_use_task pipefs system:object_r:system:low_s; + +#genfscon +genfscon proc / system:object_r:system:med +genfscon proc /sys system:object_r:system:low_s +genfscon selinuxfs / system:object_r:system:high_s:here.there + +portcon tcp 80 system:object_r:system:low_s + +netifcon eth0 system:object_r:system:low_s system:object_r:system:low_s + +nodecon 127.0.0.1 255.255.255.255 system:object_r:system:low_s:here +nodecon ::1 ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff system:object_r:system:low_s:here +