diff --git a/sediff b/sediff
index 5fd0c10..45090e9 100755
--- a/sediff
+++ b/sediff
@@ -1,5 +1,5 @@
#!/usr/bin/python
-# Copyright 2015, Tresys Technology, LLC
+# Copyright 2015-2016, Tresys Technology, LLC
#
# This file is part of SETools.
#
@@ -487,6 +487,52 @@ try:
print()
+ if all_differences or args.range_trans:
+ if diff.added_range_transitions or diff.removed_range_transitions or \
+ diff.modified_range_transitions or args.range_trans:
+ print("Range_transition Rules ({0} Added, {1} Removed, {2} Modified)".format(
+ len(diff.added_range_transitions), len(diff.removed_range_transitions),
+ len(diff.modified_range_transitions)))
+
+ if diff.added_range_transitions:
+ print(" Added Range_transition Rules: {0}".format(
+ len(diff.added_range_transitions)))
+ for r in sorted(diff.added_range_transitions):
+ print(" + {0}".format(r))
+
+ if diff.removed_range_transitions:
+ print(" Removed Range_transition Rules: {0}".format(
+ len(diff.removed_range_transitions)))
+ for r in sorted(diff.removed_range_transitions):
+ print(" - {0}".format(r))
+
+ if diff.modified_range_transitions:
+ print(" Modified Range_transition Rules: {0}".format(
+ len(diff.modified_range_transitions)))
+
+ for rule, added_default, removed_default in sorted(diff.modified_range_transitions):
+ # added brackets around range change for clarity since ranges
+ # can have '-' and spaces.
+ rule_string = \
+ "{0.ruletype} {0.source} {0.target}:{0.tclass} +[{1}] -[{2}]".format(
+ rule, added_default, removed_default)
+
+ try:
+ rule_string += " {0}".format(self.filename)
+ except:
+ pass
+
+ rule_string += ";"
+
+ try:
+ rule_string += " [ {0} ]".format(rule.conditional)
+ except:
+ pass
+
+ print(" * {0}".format(rule_string))
+
+ print()
+
except Exception as err:
if args.debug:
import traceback
diff --git a/setools/diff/__init__.py b/setools/diff/__init__.py
index 8bc045f..2eba1fb 100644
--- a/setools/diff/__init__.py
+++ b/setools/diff/__init__.py
@@ -1,4 +1,4 @@
-# Copyright 2015, Tresys Technology, LLC
+# Copyright 2015-2016, Tresys Technology, LLC
#
# This file is part of SETools.
#
@@ -17,6 +17,7 @@
# .
#
from .commons import CommonDifference
+from .mlsrules import MLSRulesDifference
from .objclass import ObjClassDifference
from .roles import RolesDifference
from .terules import TERulesDifference
@@ -26,6 +27,7 @@ __all__ = ['PolicyDifference']
class PolicyDifference(CommonDifference,
+ MLSRulesDifference,
ObjClassDifference,
RolesDifference,
TERulesDifference,
diff --git a/setools/diff/mlsrules.py b/setools/diff/mlsrules.py
new file mode 100644
index 0000000..6ccbd92
--- /dev/null
+++ b/setools/diff/mlsrules.py
@@ -0,0 +1,135 @@
+# 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 ..policyrep.exception import RuleNotConditional, RuleUseError, TERuleNoFilename
+
+from .descriptors import DiffResultDescriptor
+from .difference import Difference, SymbolWrapper, Wrapper
+from .mls import RangeWrapper
+
+
+modified_mlsrule_record = namedtuple("modified_mlsrule", ["rule",
+ "added_default",
+ "removed_default"])
+
+
+class MLSRulesDifference(Difference):
+
+ """Determine the difference in MLS rules between two policies."""
+
+ added_range_transitions = DiffResultDescriptor("diff_range_transitions")
+ removed_range_transitions = DiffResultDescriptor("diff_range_transitions")
+ modified_range_transitions = DiffResultDescriptor("diff_range_transitions")
+
+ # Lists of rules for each policy
+ _left_range_transitions = None
+ _right_range_transitions = None
+
+ def diff_range_transitions(self):
+ """Generate the difference in range_transition rules between the policies."""
+
+ self.log.info(
+ "Generating range_transition differences from {0.left_policy} to {0.right_policy}".
+ format(self))
+
+ if self._left_range_transitions is None or self._right_range_transitions is None:
+ self._create_mls_rule_lists()
+
+ self.added_range_transitions, \
+ self.removed_range_transitions, \
+ self.modified_range_transitions = self._diff_mls_rules(
+ self._expand_generator(self._left_range_transitions, MLSRuleWrapper),
+ self._expand_generator(self._right_range_transitions, MLSRuleWrapper))
+
+ #
+ # Internal functions
+ #
+ def _create_mls_rule_lists(self):
+ """Create rule lists for both policies."""
+ self._left_range_transitions = []
+ for rule in self.left_policy.mlsrules():
+ # do not expand yet, to keep memory
+ # use down as long as possible
+ if rule.ruletype == "range_transition":
+ self._left_range_transitions.append(rule)
+ else:
+ raise TypeError("Unknown rule type: {0}".format(rule.ruletype))
+
+ self._right_range_transitions = []
+ for rule in self.right_policy.mlsrules():
+ # do not expand yet, to keep memory
+ # use down as long as possible
+ if rule.ruletype == "range_transition":
+ self._right_range_transitions.append(rule)
+ else:
+ raise TypeError("Unknown rule type: {0}".format(rule.ruletype))
+
+ def _diff_mls_rules(self, left_list, right_list):
+ """Common method for comparing type_* rules."""
+ added, removed, matched = self._set_diff(left_list, right_list)
+
+ modified = []
+
+ for left_rule, right_rule in matched:
+ # Criteria for modified rules
+ # 1. change to default range
+ if RangeWrapper(left_rule.default) != RangeWrapper(right_rule.default):
+ modified.append(modified_mlsrule_record(left_rule,
+ right_rule.default,
+ left_rule.default))
+
+ return added, removed, modified
+
+ def _reset_diff(self):
+ """Reset diff results on policy changes."""
+ self.log.debug("Resetting MLS rule differences")
+ self.added_range_transitions = None
+ self.removed_range_transitions = None
+ self.modified_range_transitions = None
+
+ # Sets of rules for each policy
+ self._left_range_transitions = None
+ self._right_range_transitions = None
+
+
+class MLSRuleWrapper(Wrapper):
+
+ """Wrap MLS rules to allow set operations."""
+
+ def __init__(self, rule):
+ self.origin = rule
+ self.ruletype = rule.ruletype
+ self.source = SymbolWrapper(rule.source)
+ self.target = SymbolWrapper(rule.target)
+ self.tclass = SymbolWrapper(rule.tclass)
+ self.key = hash(rule)
+
+ def __hash__(self):
+ return self.key
+
+ def __lt__(self, other):
+ return self.key < other.key
+
+ def __eq__(self, other):
+ # because MLSRuleDifference groups rules by ruletype,
+ # the ruletype always matches.
+ return self.source == other.source and \
+ self.target == other.target and \
+ self.tclass == other.tclass
diff --git a/tests/diff.py b/tests/diff.py
index 571f8d7..b231ff0 100644
--- a/tests/diff.py
+++ b/tests/diff.py
@@ -1,4 +1,4 @@
-# Copyright 2015, Tresys Technology, LLC
+# Copyright 2015-2016, Tresys Technology, LLC
#
# This file is part of SETools.
#
@@ -736,6 +736,48 @@ class PolicyDifferenceTest(ValidateRule, unittest.TestCase):
self.assertEqual("tm_new_type", added_default)
self.assertEqual("tm_old_type", removed_default)
+ #
+ # Range_transition rules
+ #
+ def test_added_range_transition_rules(self):
+ """Diff: added range_transition rules."""
+ rules = sorted(self.diff.added_range_transitions)
+ self.assertEqual(2, len(rules))
+
+ # added rule with new type
+ self.validate_rule(rules[0], "range_transition", "added_type", "system", "infoflow4",
+ "s3")
+
+ # added rule with existing types
+ self.validate_rule(rules[1], "range_transition", "rt_added_rule_source",
+ "rt_added_rule_target", "infoflow", "s3")
+
+ def test_removed_range_transition_rules(self):
+ """Diff: removed range_transition rules."""
+ rules = sorted(self.diff.removed_range_transitions)
+ self.assertEqual(2, len(rules))
+
+ # removed rule with new type
+ self.validate_rule(rules[0], "range_transition", "removed_type", "system", "infoflow4",
+ "s1")
+
+ # removed rule with existing types
+ self.validate_rule(rules[1], "range_transition", "rt_removed_rule_source",
+ "rt_removed_rule_target", "infoflow", "s1")
+
+ def test_modified_range_transition_rules(self):
+ """Diff: modified range_transition rules."""
+ l = sorted(self.diff.modified_range_transitions)
+ self.assertEqual(1, len(l))
+
+ rule, added_default, removed_default = l[0]
+ self.assertEqual("range_transition", rule.ruletype)
+ self.assertEqual("rt_matched_source", rule.source)
+ self.assertEqual("system", rule.target)
+ self.assertEqual("infoflow", rule.tclass)
+ self.assertEqual("s0:c0,c4 - s1:c0.c2,c4", added_default)
+ self.assertEqual("s2:c0 - s3:c0.c2", removed_default)
+
class PolicyDifferenceTestNoDiff(unittest.TestCase):
@@ -876,3 +918,15 @@ class PolicyDifferenceTestNoDiff(unittest.TestCase):
def test_modified_type_members(self):
"""NoDiff: no modified type_member rules."""
self.assertFalse(self.diff.modified_type_members)
+
+ def test_added_range_transitions(self):
+ """NoDiff: no added range_transition rules."""
+ self.assertFalse(self.diff.added_range_transitions)
+
+ def test_removed_range_transitions(self):
+ """NoDiff: no removed range_transition rules."""
+ self.assertFalse(self.diff.removed_range_transitions)
+
+ def test_modified_range_transitions(self):
+ """NoDiff: no modified range_transition rules."""
+ self.assertFalse(self.diff.modified_range_transitions)
diff --git a/tests/diff_left.conf b/tests/diff_left.conf
index 2db5cbc..5fe13bf 100644
--- a/tests/diff_left.conf
+++ b/tests/diff_left.conf
@@ -92,20 +92,38 @@ class modified_remove_perm
class modified_change_common
inherits removed_common
-sensitivity low_s;
-sensitivity medium_s alias med;
-sensitivity high_s;
+sensitivity s0;
+sensitivity s1;
+sensitivity s2;
+sensitivity s3;
+sensitivity s40;
+sensitivity s41;
+sensitivity s42;
+sensitivity s43;
+sensitivity s44;
+sensitivity s45;
+sensitivity s46;
-dominance { low_s med high_s }
+dominance { s0 s1 s2 s3 s40 s41 s42 s43 s44 s45 s46 }
-category here;
-category there;
-category elsewhere alias lost;
+category c0;
+category c1;
+category c2;
+category c3;
+category c4;
#level decl
-level low_s:here.there;
-level med:here, elsewhere;
-level high_s:here.lost;
+level s0:c0.c4;
+level s1:c0.c4;
+level s2:c0.c4;
+level s3:c0.c4;
+level s40:c0.c4;
+level s41:c0.c4;
+level s42:c0.c4;
+level s43:c0.c4;
+level s44:c0.c4;
+level s45:c0.c4;
+level s46:c0.c4;
#some constraints
mlsconstrain infoflow hi_r ((l1 dom l2) or (t1 == mls_exempt));
@@ -486,32 +504,78 @@ type_member tm_unioned_perm_via_attr system:infoflow2 system;
type_member tm_unioned_perm_via_attr_A_t system:infoflow2 system;
type_member tm_unioned_perm_via_attr_B_t system:infoflow2 system;
+# range_transition rule differences
+type rt_matched_source;
+type rt_matched_target;
+range_transition rt_matched_source rt_matched_target:infoflow s0;
+
+type rt_removed_rule_source;
+type rt_removed_rule_target;
+range_transition rt_removed_rule_source rt_removed_rule_target:infoflow s1;
+
+type rt_added_rule_source;
+type rt_added_rule_target;
+
+range_transition rt_matched_source system:infoflow s2:c0 - s3:c0.c2;
+
+range_transition removed_type system:infoflow4 s1;
+
+# range transitions cannot be conditional.
+#type rt_move_to_bool;
+#bool rt_move_to_bool_b false;
+#range_transition rt_move_to_bool system:infoflow3 s0;
+
+#type rt_move_from_bool;
+#bool rt_move_from_bool_b false;
+#if (rt_move_from_bool_b) {
+#range_transition rt_move_from_bool system:infoflow4 s0;
+#}
+
+#type rt_switch_block;
+#bool rt_switch_block_b false;
+#if (rt_switch_block_b) {
+#range_transition system rt_switch_block:infoflow5 s0;
+#range_transition system rt_switch_block:infoflow6 s0;
+#} else {
+#range_transition system rt_switch_block:infoflow7 s0;
+#}
+
+attribute rt_match_rule_by_attr;
+type rt_match_rule_by_attr_A_t, rt_match_rule_by_attr;
+type rt_match_rule_by_attr_B_t, rt_match_rule_by_attr;
+range_transition rt_match_rule_by_attr system:infoflow2 s0;
+
+attribute rt_unioned_perm_via_attr;
+type rt_unioned_perm_via_attr_A_t, rt_unioned_perm_via_attr;
+type rt_unioned_perm_via_attr_B_t, rt_unioned_perm_via_attr;
+range_transition rt_unioned_perm_via_attr system:infoflow2 s0;
+range_transition rt_unioned_perm_via_attr_A_t system:infoflow2 s0;
+
################################################################################
#users
-user system roles system level med range low_s - high_s:here.lost;
+user system roles system level s0 range s0 - s46:c0.c4;
#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
+sid kernel system:system:system:s0
+sid security system:system:system:s0
#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;
+fs_use_trans devpts system:object_r:system:s0;
+fs_use_xattr ext3 system:object_r:system:s0;
+fs_use_task pipefs system:object_r:system:s0;
#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
+genfscon proc / system:object_r:system:s0
+genfscon proc /sys system:object_r:system:s0
+genfscon selinuxfs / system:object_r:system:s0
-portcon tcp 80 system:object_r:system:low_s
+portcon tcp 80 system:object_r:system:s0
-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
+netifcon eth0 system:object_r:system:s0 system:object_r:system:s0
+nodecon 127.0.0.1 255.255.255.255 system:object_r:system:s0
+nodecon ::1 ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff system:object_r:system:s0
diff --git a/tests/diff_right.conf b/tests/diff_right.conf
index c174541..55802d5 100644
--- a/tests/diff_right.conf
+++ b/tests/diff_right.conf
@@ -92,20 +92,39 @@ class modified_remove_perm
class modified_change_common
inherits added_common
-sensitivity low_s;
-sensitivity medium_s alias med;
-sensitivity high_s;
+sensitivity s0;
+sensitivity s1;
+sensitivity s2;
+sensitivity s3;
+sensitivity s40;
+sensitivity s41;
+sensitivity s42;
+sensitivity s43;
+sensitivity s44;
+sensitivity s45;
+sensitivity s46;
-dominance { low_s med high_s }
+dominance { s0 s1 s2 s3 s40 s41 s42 s43 s44 s45 s46 }
-category here;
-category there;
-category elsewhere alias lost;
+category c0;
+category c0a;
+category c1;
+category c2;
+category c3;
+category c4;
#level decl
-level low_s:here.there;
-level med:here, elsewhere;
-level high_s:here.lost;
+level s0:c0.c4;
+level s1:c0.c4;
+level s2:c0.c4;
+level s3:c0.c4;
+level s40:c0.c4;
+level s41:c0.c4;
+level s42:c0.c4;
+level s43:c0.c4;
+level s44:c0.c4;
+level s45:c0.c4;
+level s46:c0.c4;
#some constraints
mlsconstrain infoflow hi_r ((l1 dom l2) or (t1 == mls_exempt));
@@ -486,32 +505,78 @@ type_member tm_unioned_perm_via_attr system:infoflow2 system;
type_member tm_unioned_perm_via_attr_A_t system:infoflow2 system;
type_member tm_unioned_perm_via_attr_B_t system:infoflow2 system;
+# range_transition rule differences
+type rt_matched_source;
+type rt_matched_target;
+range_transition rt_matched_source rt_matched_target:infoflow s0;
+
+type rt_removed_rule_source;
+type rt_removed_rule_target;
+
+type rt_added_rule_source;
+type rt_added_rule_target;
+range_transition rt_added_rule_source rt_added_rule_target:infoflow s3;
+
+range_transition rt_matched_source system:infoflow s0:c0,c4 - s1:c0.c2,c4;
+
+range_transition added_type system:infoflow4 s3;
+
+# range transitions cannot be conditional.
+#type rt_move_to_bool;
+#bool rt_move_to_bool_b false;
+#if (rt_move_to_bool_b) {
+#range_transition rt_move_to_bool system:infoflow3 s0;
+#}
+
+#type rt_move_from_bool;
+#bool rt_move_from_bool_b false;
+#range_transition rt_move_from_bool system:infoflow4 s0;
+
+#type rt_switch_block;
+#bool rt_switch_block_b false;
+#if (rt_switch_block_b) {
+#range_transition system rt_switch_block:infoflow5 s0;
+#} else {
+#range_transition system rt_switch_block:infoflow6 s0;
+#range_transition system rt_switch_block:infoflow7 s0;
+#}
+
+attribute rt_match_rule_by_attr;
+type rt_match_rule_by_attr_A_t, rt_match_rule_by_attr;
+type rt_match_rule_by_attr_B_t, rt_match_rule_by_attr;
+range_transition rt_match_rule_by_attr system:infoflow2 s0;
+
+attribute rt_unioned_perm_via_attr;
+type rt_unioned_perm_via_attr_A_t, rt_unioned_perm_via_attr;
+type rt_unioned_perm_via_attr_B_t, rt_unioned_perm_via_attr;
+range_transition rt_unioned_perm_via_attr system:infoflow2 s0;
+range_transition rt_unioned_perm_via_attr_B_t system:infoflow2 s0;
+
################################################################################
#users
-user system roles system level med range low_s - high_s:here.lost;
+user system roles system level s0 range s0 - s46:c0.c4;
#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
+sid kernel system:system:system:s0
+sid security system:system:system:s0
#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;
+fs_use_trans devpts system:object_r:system:s0;
+fs_use_xattr ext3 system:object_r:system:s0;
+fs_use_task pipefs system:object_r:system:s0;
#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
+genfscon proc / system:object_r:system:s0
+genfscon proc /sys system:object_r:system:s0
+genfscon selinuxfs / system:object_r:system:s0
-portcon tcp 80 system:object_r:system:low_s
+portcon tcp 80 system:object_r:system:s0
-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
+netifcon eth0 system:object_r:system:s0 system:object_r:system:s0
+nodecon 127.0.0.1 255.255.255.255 system:object_r:system:s0
+nodecon ::1 ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff system:object_r:system:s0