From a4d4920d3c25835802b4d6f18fe6798e569b0ed4 Mon Sep 17 00:00:00 2001 From: Chris PeBenito Date: Wed, 10 Feb 2016 11:44:41 -0500 Subject: [PATCH] PolicyDifference: implement typebounds diff. Closes #67 --- sediff | 25 +++++++- setools/diff/__init__.py | 2 + setools/diff/bounds.py | 112 ++++++++++++++++++++++++++++++++++ setools/diff/difference.py | 6 +- setools/policyrep/bounds.py | 3 + tests/diff.py | 58 ++++++++++++++++++ tests/diff_left.conf | 19 ++++++ tests/diff_left_standard.conf | 19 ++++++ tests/diff_right.conf | 19 ++++++ 9 files changed, 259 insertions(+), 4 deletions(-) create mode 100644 setools/diff/bounds.py diff --git a/sediff b/sediff index bb0cbfc..5414671 100755 --- a/sediff +++ b/sediff @@ -91,6 +91,7 @@ other.add_argument("--default", action="store_true", help="Print default_* diffe other.add_argument("--property", action="store_true", help="Print policy property differences (handle_unknown, version, MLS)") other.add_argument("--polcap", action="store_true", help="Print policy capability differences") +other.add_argument("--typebounds", action="store_true", help="Print typebounds differences") args = parser.parse_args() @@ -101,7 +102,7 @@ all_differences = not any((args.class_, args.common, args.type_, args.attribute, args.role_trans, args.range_trans, args.initialsid, args.genfscon, args.netifcon, args.nodecon, args.portcon, args.fs_use, args.polcap, args.property, args.default, args.constrain, args.mlsconstrain, - args.validatetrans, args.mlsvalidatetrans)) + args.validatetrans, args.mlsvalidatetrans, args.typebounds)) if args.debug: logging.basicConfig(level=logging.DEBUG, @@ -1087,6 +1088,28 @@ try: print() + if all_differences or args.typebounds: + if diff.added_typebounds or diff.removed_typebounds or args.typebounds: + print("Typebounds ({0} Added, {1} Removed, {2} Modified)".format( + len(diff.added_typebounds), len(diff.removed_typebounds), + len(diff.modified_typebounds))) + if diff.added_typebounds and not args.stats: + print(" Added Typebounds: {0}".format(len(diff.added_typebounds))) + for d in sorted(diff.added_typebounds): + print(" + {0}".format(d)) + if diff.removed_typebounds and not args.stats: + print(" Removed Typebounds: {0}".format(len(diff.removed_typebounds))) + for d in sorted(diff.removed_typebounds): + print(" - {0}".format(d)) + if diff.modified_typebounds and not args.stats: + print(" Modified Typebounds: {0}".format(len(diff.modified_typebounds))) + for bound, added_bound, removed_bound in sorted( + diff.modified_typebounds, key=lambda x: x.rule): + print(" * {0.ruletype} +{1} -{2} {0.child};".format( + bound, added_bound, removed_bound)) + + print() + except Exception as err: if args.debug: import traceback diff --git a/setools/diff/__init__.py b/setools/diff/__init__.py index afc921a..612574c 100644 --- a/setools/diff/__init__.py +++ b/setools/diff/__init__.py @@ -17,6 +17,7 @@ # . # from .bool import BooleansDifference +from .bounds import BoundsDifference from .commons import CommonDifference from .constraints import ConstraintsDifference from .default import DefaultsDifference @@ -42,6 +43,7 @@ __all__ = ['PolicyDifference'] class PolicyDifference(BooleansDifference, + BoundsDifference, CategoriesDifference, CommonDifference, ConstraintsDifference, diff --git a/setools/diff/bounds.py b/setools/diff/bounds.py new file mode 100644 index 0000000..e23c91f --- /dev/null +++ b/setools/diff/bounds.py @@ -0,0 +1,112 @@ +# Copyright 2016, 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 +# . +# +from collections import namedtuple + +from .descriptors import DiffResultDescriptor +from .difference import Difference, SymbolWrapper, Wrapper + + +modified_bounds_record = namedtuple("modified_bound", ["rule", "added_bound", "removed_bound"]) + + +class BoundsDifference(Difference): + + """Determine the difference in *bounds between two policies.""" + + added_typebounds = DiffResultDescriptor("diff_typebounds") + removed_typebounds = DiffResultDescriptor("diff_typebounds") + modified_typebounds = DiffResultDescriptor("diff_typebounds") + + # Lists of rules for each policy + _left_typebounds = None + _right_typebounds = None + + def diff_typebounds(self): + """Generate the difference in typebound rules between the policies.""" + + self.log.info("Generating typebounds differences from {0.left_policy} to {0.right_policy}". + format(self)) + + if self._left_typebounds is None or self._right_typebounds is None: + self._create_typebound_lists() + + self.added_typebounds, self.removed_typebounds, matched_typebounds = self._set_diff( + (BoundsWrapper(c) for c in self._left_typebounds), + (BoundsWrapper(c) for c in self._right_typebounds), + key=lambda b: str(b.child)) + + self.modified_typebounds = [] + + for left_bound, right_bound in matched_typebounds: + if SymbolWrapper(left_bound.parent) != SymbolWrapper(right_bound.parent): + self.modified_typebounds.append(modified_bounds_record( + left_bound, right_bound.parent, left_bound.parent)) + + # + # Internal functions + # + def _create_typebound_lists(self): + """Create rule lists for both policies.""" + self._left_typebounds = [] + for rule in self.left_policy.bounds(): + if rule.ruletype == "typebounds": + self._left_typebounds.append(rule) + else: + self.log.error("Unknown rule type: {0} (This is an SETools bug)". + format(rule.ruletype)) + + self._right_typebounds = [] + for rule in self.right_policy.bounds(): + if rule.ruletype == "typebounds": + self._right_typebounds.append(rule) + else: + self.log.error("Unknown rule type: {0} (This is an SETools bug)". + format(rule.ruletype)) + + def _reset_diff(self): + """Reset diff results on policy changes.""" + self.log.debug("Resetting all *bounds differences") + self.added_typebounds = None + self.removed_typebounds = None + + # Sets of rules for each policy + self._left_typebounds = None + self._right_typebounds = None + + +class BoundsWrapper(Wrapper): + + """Wrap *bounds for diff purposes.""" + + def __init__(self, rule): + self.origin = rule + self.ruletype = rule.ruletype + self.parent = SymbolWrapper(rule.parent) + self.child = SymbolWrapper(rule.child) + self.key = hash(rule) + + def __hash__(self): + return self.key + + def __lt__(self, other): + return self.key < other.key + + def __eq__(self, other): + return self.ruletype == other.ruletype and \ + self.child == other.child diff --git a/setools/diff/difference.py b/setools/diff/difference.py index 189c67d..0ea4f3b 100644 --- a/setools/diff/difference.py +++ b/setools/diff/difference.py @@ -72,7 +72,7 @@ class Difference(object): yield Wrapper(expanded_rule) @staticmethod - def _set_diff(left, right): + def _set_diff(left, right, key=None): """ Standard diff of two sets. @@ -108,8 +108,8 @@ class Difference(object): # instead of giving wrong results. If there is a better way to, # ensure the items match up, please let me know how or submit a patch. matched_items = set() - left_matched_items = sorted((left_items - removed_items)) - right_matched_items = sorted((right_items - added_items)) + left_matched_items = sorted((left_items - removed_items), key=key) + right_matched_items = sorted((right_items - added_items), key=key) assert len(left_matched_items) == len(right_matched_items), \ "Matched items assertion failure (this is an SETools bug), {0} != {1}". \ format(len(left_matched_items), len(right_matched_items)) diff --git a/setools/policyrep/bounds.py b/setools/policyrep/bounds.py index 91508ab..8437374 100644 --- a/setools/policyrep/bounds.py +++ b/setools/policyrep/bounds.py @@ -37,6 +37,9 @@ class Bounds(PolicySymbol): def __str__(self): return "{0.ruletype} {0.parent} {0.child};".format(self) + def __hash__(self): + return hash("{0.ruletype}|{0.child};".format(self)) + ruletype = "typebounds" @property diff --git a/tests/diff.py b/tests/diff.py index 1543387..08c7683 100644 --- a/tests/diff.py +++ b/tests/diff.py @@ -1521,6 +1521,40 @@ class PolicyDifferenceTest(ValidateRule, unittest.TestCase): ['l1', 'l2', 'dom', 'h1', 'h2', 'dom', 'and', 't3', set(["mls_exempt"]), '==', 'or'], mlsvalidatetrans.postfix_expression()) + # + # typebounds + # + def test_added_typebounds(self): + """Diff: added typebounds.""" + l = sorted(self.diff.added_typebounds) + self.assertEqual(1, len(l)) + + bounds = l[0] + self.assertEqual("typebounds", bounds.ruletype) + self.assertEqual("added_parent", bounds.parent) + self.assertEqual("added_child", bounds.child) + + def test_removed_typebounds(self): + """Diff: removed typebounds.""" + l = sorted(self.diff.removed_typebounds) + self.assertEqual(1, len(l)) + + bounds = l[0] + self.assertEqual("typebounds", bounds.ruletype) + self.assertEqual("removed_parent", bounds.parent) + self.assertEqual("removed_child", bounds.child) + + def test_modified_typebounds(self): + """Diff: modified typebounds.""" + l = sorted(self.diff.modified_typebounds, key=lambda x: x.rule) + self.assertEqual(1, len(l)) + + bounds, added_bound, removed_bound = l[0] + self.assertEqual("typebounds", bounds.ruletype) + self.assertEqual("mod_child", bounds.child) + self.assertEqual("mod_parent_added", added_bound) + self.assertEqual("mod_parent_removed", removed_bound) + class PolicyDifferenceTestNoDiff(unittest.TestCase): @@ -1899,6 +1933,18 @@ class PolicyDifferenceTestNoDiff(unittest.TestCase): """NoDiff: no removed mlsvalidatetrans.""" self.assertFalse(self.diff.removed_mlsvalidatetrans) + def test_added_typebounds(self): + """NoDiff: no added typebounds.""" + self.assertFalse(self.diff.added_typebounds) + + def test_removed_typebounds(self): + """NoDiff: no removed typebounds.""" + self.assertFalse(self.diff.removed_typebounds) + + def test_modified_typebounds(self): + """NoDiff: no modified typebounds.""" + self.assertFalse(self.diff.modified_typebounds) + class PolicyDifferenceTestMLStoStandard(unittest.TestCase): @@ -2294,3 +2340,15 @@ class PolicyDifferenceTestMLStoStandard(unittest.TestCase): self.assertEqual( sum(1 for m in self.diff.left_policy.constraints() if m.ruletype == "mlsvalidatetrans"), len(self.diff.removed_mlsvalidatetrans)) + + def test_added_typebounds(self): + """MLSvsStandardDiff: no added typebounds.""" + self.assertFalse(self.diff.added_typebounds) + + def test_removed_typebounds(self): + """MLSvsStandardDiff: no removed typebounds.""" + self.assertFalse(self.diff.removed_typebounds) + + def test_modified_typebounds(self): + """MLSvsStandardDiff: no modified typebounds.""" + self.assertFalse(self.diff.modified_typebounds) diff --git a/tests/diff_left.conf b/tests/diff_left.conf index fe7b17f..30fb777 100644 --- a/tests/diff_left.conf +++ b/tests/diff_left.conf @@ -635,6 +635,25 @@ role role_tr_new_role; role_transition role_tr_matched_source role_tr_matched_target:infoflow3 role_tr_old_role; ################################################################################ +# matching typebounds +type match_parent; +type match_child; +typebounds match_parent match_child; + +# removed typebounds +type removed_parent; +type removed_child; +typebounds removed_parent removed_child; + +# added typebounds +type added_parent; +type added_child; + +# modified typebounds +type mod_parent_removed; +type mod_parent_added; +type mod_child; +typebounds mod_parent_removed mod_child; # policycaps policycap open_perms; diff --git a/tests/diff_left_standard.conf b/tests/diff_left_standard.conf index b270bd4..419885e 100644 --- a/tests/diff_left_standard.conf +++ b/tests/diff_left_standard.conf @@ -557,6 +557,25 @@ role role_tr_new_role; role_transition role_tr_matched_source role_tr_matched_target:infoflow3 role_tr_old_role; ################################################################################ +# matching typebounds +type match_parent; +type match_child; +typebounds match_parent match_child; + +# removed typebounds +type removed_parent; +type removed_child; +typebounds removed_parent removed_child; + +# added typebounds +type added_parent; +type added_child; + +# modified typebounds +type mod_parent_removed; +type mod_parent_added; +type mod_child; +typebounds mod_parent_removed mod_child; # policycaps policycap open_perms; diff --git a/tests/diff_right.conf b/tests/diff_right.conf index 29f1f21..eafea65 100644 --- a/tests/diff_right.conf +++ b/tests/diff_right.conf @@ -635,6 +635,25 @@ role role_tr_new_role; role_transition role_tr_matched_source role_tr_matched_target:infoflow3 role_tr_new_role; ################################################################################ +# matching typebounds +type match_parent; +type match_child; +typebounds match_parent match_child; + +# removed typebounds +type removed_parent; +type removed_child; + +# added typebounds +type added_parent; +type added_child; +typebounds added_parent added_child; + +# modified typebounds +type mod_parent_removed; +type mod_parent_added; +type mod_child; +typebounds mod_parent_added mod_child; # policycaps policycap open_perms;