Implement infoflow subgraph to handle min weight and excluded types.

Infoflow now will create a complete graph for the policy and then create
a subgraph to filter out nodes based on excluded types and edges based on
minimum weight.  The main graph will only need to be rebuilt if there is
a change in the permission map.  While this is a little more expensive for
seinfoflow, it should make interactive analysis in apol faster since
repeatedly deriving a subgraph will be faster than repeatedly rebuilding
the entire graph.
This commit is contained in:
Chris PeBenito 2014-11-09 20:00:01 -05:00
parent 5bf48a4a1f
commit 8a07be100f
2 changed files with 86 additions and 244 deletions

View File

@ -45,6 +45,7 @@ class InfoFlowAnalysis(object):
self.set_perm_map(perm_map)
self.set_exclude(exclude)
self.rebuildgraph = True
self.rebuildsubgraph = True
self.G = nx.DiGraph()
@ -63,7 +64,7 @@ class InfoFlowAnalysis(object):
"Min information flow weight must be an integer 1-10.")
self.minweight = w
self.rebuildgraph = True
self.rebuildsubgraph = True
def set_perm_map(self, perm_map):
"""
@ -85,6 +86,7 @@ class InfoFlowAnalysis(object):
self.perm_map = perm_map
self.rebuildgraph = True
self.rebuildsubgraph = True
def set_exclude(self, exclude):
"""
@ -96,6 +98,8 @@ class InfoFlowAnalysis(object):
self.exclude = [self.policy.lookup_type(t) for t in exclude]
self.rebuildsubgraph = True
def __get_steps(self, path):
"""
Generator which returns the source, target, and associated rules
@ -135,12 +139,12 @@ class InfoFlowAnalysis(object):
s = self.policy.lookup_type(source)
t = self.policy.lookup_type(target)
if self.rebuildgraph:
self._build_graph()
if self.rebuildsubgraph:
self._build_subgraph()
if s in self.G and t in self.G:
if s in self.subG and t in self.subG:
try:
path = nx.shortest_path(self.G, s, t)
path = nx.shortest_path(self.subG, s, t)
except nx.exception.NetworkXNoPath:
pass
else:
@ -171,12 +175,12 @@ class InfoFlowAnalysis(object):
s = self.policy.lookup_type(source)
t = self.policy.lookup_type(target)
if self.rebuildgraph:
self._build_graph()
if self.rebuildsubgraph:
self._build_subgraph()
if s in self.G and t in self.G:
if s in self.subG and t in self.subG:
try:
paths = nx.all_simple_paths(self.G, s, t, maxlen)
paths = nx.all_simple_paths(self.subG, s, t, maxlen)
except nx.exception.NetworkXNoPath:
pass
else:
@ -203,12 +207,12 @@ class InfoFlowAnalysis(object):
s = self.policy.lookup_type(source)
t = self.policy.lookup_type(target)
if self.rebuildgraph:
self._build_graph()
if self.rebuildsubgraph:
self._build_subgraph()
if s in self.G and t in self.G:
if s in self.subG and t in self.subG:
try:
paths = nx.all_shortest_paths(self.G, s, t)
paths = nx.all_shortest_paths(self.subG, s, t)
except nx.exception.NetworkXNoPath:
pass
else:
@ -231,10 +235,10 @@ class InfoFlowAnalysis(object):
"""
s = self.policy.lookup_type(type_)
if self.rebuildgraph:
self._build_graph()
if self.rebuildsubgraph:
self._build_subgraph()
for source, target, data in self.G.out_edges_iter(s, data=True):
for source, target, data in self.subG.out_edges_iter(s, data=True):
yield source, target, data["rules"]
def get_stats(self):
@ -254,13 +258,18 @@ class InfoFlowAnalysis(object):
# (Internal) Graph building functions
#
#
# 1. __build_graph determines the flow in each direction for each TE
# rule and then expands the rule (ignoring excluded types)
# 2. __add_flow Simply creates edges in the appropriate direction.
# (decrease chance of coding errors for graph operations)
# 3. __add_edge does the actual graph insertions. Nodes are implictly
# 1. _build_graph determines the flow in each direction for each TE
# rule and then expands the rule. All information flows are
# included in this main graph: memory is traded off for efficiency
# as the main graph should only need to be rebuilt if permission
# weights change.
# 2. __add_edge does the actual graph insertions. Nodes are implictly
# created by the edge additions, i.e. types that have no info flow
# due to permission weights or are excluded do not appear in the graph.
# do not appear in the graph.
# 3. _build_subgraph derives a subgraph which removes all excluded
# types (nodes) and edges (information flows) which are below the
# minimum weight. This subgraph is rebuilt only if the main graph
# is rebuilt or the minimum weight or excluded types change.
def __add_edge(self, source, target, rule, weight):
# use capacity to store the info flow weight so
# we can use network flow algorithms naturally.
@ -274,18 +283,6 @@ class InfoFlowAnalysis(object):
self.G.add_edge(
source, target, capacity=weight, weight=1, rules=[rule])
def __add_flow(self, source, target, rule, ww, rw):
assert max(ww, rw) >= self.minweight
# only add flows if they actually flow
# in our out of the source type type
if source != target:
if ww >= self.minweight:
self.__add_edge(source, target, rule, ww)
if rw >= self.minweight:
self.__add_edge(target, source, rule, rw)
def _build_graph(self):
self.G.clear()
@ -295,12 +292,33 @@ class InfoFlowAnalysis(object):
(rweight, wweight) = self.perm_map.rule_weight(r)
# 1. only proceed if weight meets or exceeds the minimum weight
# 2. expand source and target to handle attributes
# 3. ignore flow if one of the types is in the exclude list
if max(rweight, wweight) >= self.minweight:
for s, t in itertools.product(r.source.expand(), r.target.expand()):
if s not in self.exclude and t not in self.exclude:
self.__add_flow(s, t, r, wweight, rweight)
for s, t in itertools.product(r.source.expand(), r.target.expand()):
# only add flows if they actually flow
# in or out of the source type type
if s != t:
if wweight:
self.__add_edge(s, t, r, wweight)
if rweight:
self.__add_edge(t, s, r, rweight)
self.rebuildgraph = False
self.rebuildsubgraph = True
def _build_subgraph(self):
if self.rebuildgraph:
self._build_graph()
# delete excluded types from subgraph
nodes = [n for n in self.G.nodes() if n not in self.exclude]
self.subG = self.G.subgraph(nodes)
# delete edges below minimum weight
delete_list = []
for s, t, data in self.subG.edges_iter(data=True):
if data['capacity'] < self.minweight:
delete_list.append((s, t))
self.subG.remove_edges_from(delete_list)
self.rebuildsubgraph = False

View File

@ -25,14 +25,20 @@ from setools.permmap import PermissionMap
from setools.policyrep.rule import RuleNotConditional
# Note: the testing for having correct rules on every edge is only
# performed once on the full graph, since it is assumed that NetworkX's
# Digraph.subgraph() function correctly copies the edge attributes into
# the subgraph.
class InfoFlowAnalysisTest(unittest.TestCase):
def setUp(self):
self.p = SELinuxPolicy("tests/infoflow.conf")
self.m = PermissionMap("tests/perm_map")
def test_001_no_minimum(self):
"""Information flow analysis with no minimum weight."""
def test_001_full_graph(self):
"""Information flow analysis full graph."""
a = InfoFlowAnalysis(self.p, self.m)
a._build_graph()
@ -50,8 +56,9 @@ class InfoFlowAnalysisTest(unittest.TestCase):
node9 = self.p.lookup_type("node9")
nodes = set(a.G.nodes_iter())
self.assertSetEqual(set(
[disconnected1, disconnected2, node1, node2, node3, node4, node5, node6, node7, node8, node9]), nodes)
self.assertSetEqual(set([disconnected1, disconnected2, node1,
node2, node3, node4, node5,
node6, node7, node8, node9]), nodes)
edges = set(a.G.out_edges_iter())
self.assertSetEqual(set([(disconnected1, disconnected2),
@ -186,7 +193,7 @@ class InfoFlowAnalysisTest(unittest.TestCase):
"""Information flow analysis with minimum weight 3."""
a = InfoFlowAnalysis(self.p, self.m, minweight=3)
a._build_graph()
a._build_subgraph()
disconnected1 = self.p.lookup_type("disconnected1")
disconnected2 = self.p.lookup_type("disconnected2")
@ -200,12 +207,14 @@ class InfoFlowAnalysisTest(unittest.TestCase):
node8 = self.p.lookup_type("node8")
node9 = self.p.lookup_type("node9")
nodes = set(a.G.nodes_iter())
self.assertSetEqual(set([disconnected1, disconnected2, node1,
node2, node3, node4, node5,
node6, node7, node8, node9]), nodes)
# don't test nodes, as disconnected nodes
# are not removed by subgraph generation
#nodes = set(a.subG.nodes_iter())
#self.assertSetEqual(set([disconnected1, disconnected2, node1,
# node2, node3, node4, node5,
# node6, node7, node8, node9]), nodes)
edges = set(a.G.out_edges_iter())
edges = set(a.subG.out_edges_iter())
self.assertSetEqual(set([(disconnected1, disconnected2),
(disconnected2, disconnected1),
(node1, node2),
@ -218,117 +227,11 @@ class InfoFlowAnalysisTest(unittest.TestCase):
(node8, node9),
(node9, node8)]), edges)
r = a.G.edge[disconnected1][disconnected2]["rules"]
self.assertEqual(len(r), 1)
self.assertEqual(r[0].ruletype, "allow")
self.assertEqual(r[0].source, "disconnected1")
self.assertEqual(r[0].target, "disconnected2")
self.assertEqual(r[0].tclass, "infoflow2")
self.assertSetEqual(set(["super"]), r[0].perms)
self.assertRaises(RuleNotConditional, getattr, r[0], "conditional")
r = a.G.edge[disconnected2][disconnected1]["rules"]
self.assertEqual(len(r), 1)
self.assertEqual(r[0].ruletype, "allow")
self.assertEqual(r[0].source, "disconnected1")
self.assertEqual(r[0].target, "disconnected2")
self.assertEqual(r[0].tclass, "infoflow2")
self.assertSetEqual(set(["super"]), r[0].perms)
self.assertRaises(RuleNotConditional, getattr, r[0], "conditional")
r = sorted(a.G.edge[node1][node2]["rules"])
self.assertEqual(len(r), 2)
self.assertEqual(r[0].ruletype, "allow")
self.assertEqual(r[0].source, "node1")
self.assertEqual(r[0].target, "node2")
self.assertEqual(r[0].tclass, "infoflow")
self.assertSetEqual(set(["med_w"]), r[0].perms)
self.assertRaises(RuleNotConditional, getattr, r[0], "conditional")
self.assertEqual(r[1].ruletype, "allow")
self.assertEqual(r[1].source, "node2")
self.assertEqual(r[1].target, "node1")
self.assertEqual(r[1].tclass, "infoflow")
self.assertSetEqual(set(["hi_r"]), r[1].perms)
self.assertRaises(RuleNotConditional, getattr, r[1], "conditional")
r = sorted(a.G.edge[node1][node3]["rules"])
self.assertEqual(len(r), 1)
self.assertEqual(r[0].ruletype, "allow")
self.assertEqual(r[0].source, "node3")
self.assertEqual(r[0].target, "node1")
self.assertEqual(r[0].tclass, "infoflow")
self.assertSetEqual(set(["low_r", "med_r"]), r[0].perms)
self.assertRaises(RuleNotConditional, getattr, r[0], "conditional")
r = sorted(a.G.edge[node2][node4]["rules"])
self.assertEqual(len(r), 1)
self.assertEqual(r[0].ruletype, "allow")
self.assertEqual(r[0].source, "node2")
self.assertEqual(r[0].target, "node4")
self.assertEqual(r[0].tclass, "infoflow")
self.assertSetEqual(set(["hi_w"]), r[0].perms)
self.assertRaises(RuleNotConditional, getattr, r[0], "conditional")
r = sorted(a.G.edge[node4][node6]["rules"])
self.assertEqual(len(r), 1)
self.assertEqual(r[0].ruletype, "allow")
self.assertEqual(r[0].source, "node4")
self.assertEqual(r[0].target, "node6")
self.assertEqual(r[0].tclass, "infoflow2")
self.assertSetEqual(set(["hi_w"]), r[0].perms)
self.assertRaises(RuleNotConditional, getattr, r[0], "conditional")
r = sorted(a.G.edge[node5][node8]["rules"])
self.assertEqual(len(r), 1)
self.assertEqual(r[0].ruletype, "allow")
self.assertEqual(r[0].source, "node5")
self.assertEqual(r[0].target, "node8")
self.assertEqual(r[0].tclass, "infoflow2")
self.assertSetEqual(set(["hi_w"]), r[0].perms)
self.assertRaises(RuleNotConditional, getattr, r[0], "conditional")
r = sorted(a.G.edge[node6][node5]["rules"])
self.assertEqual(len(r), 1)
self.assertEqual(r[0].ruletype, "allow")
self.assertEqual(r[0].source, "node5")
self.assertEqual(r[0].target, "node6")
self.assertEqual(r[0].tclass, "infoflow")
self.assertSetEqual(set(["med_r"]), r[0].perms)
self.assertRaises(RuleNotConditional, getattr, r[0], "conditional")
r = sorted(a.G.edge[node6][node7]["rules"])
self.assertEqual(len(r), 1)
self.assertEqual(r[0].ruletype, "allow")
self.assertEqual(r[0].source, "node6")
self.assertEqual(r[0].target, "node7")
self.assertEqual(r[0].tclass, "infoflow")
self.assertSetEqual(set(["hi_w"]), r[0].perms)
self.assertRaises(RuleNotConditional, getattr, r[0], "conditional")
r = sorted(a.G.edge[node8][node9]["rules"])
self.assertEqual(len(r), 1)
self.assertEqual(r[0].ruletype, "allow")
self.assertEqual(r[0].source, "node8")
self.assertEqual(r[0].target, "node9")
self.assertEqual(r[0].tclass, "infoflow2")
self.assertSetEqual(set(["super"]), r[0].perms)
self.assertRaises(RuleNotConditional, getattr, r[0], "conditional")
r = sorted(a.G.edge[node9][node8]["rules"])
self.assertEqual(len(r), 1)
self.assertEqual(r[0].ruletype, "allow")
self.assertEqual(r[0].source, "node8")
self.assertEqual(r[0].target, "node9")
self.assertEqual(r[0].tclass, "infoflow2")
self.assertSetEqual(set(["super"]), r[0].perms)
self.assertRaises(RuleNotConditional, getattr, r[0], "conditional")
def test_200_minimum_8(self):
"""Information flow analysis with minimum weight 8."""
a = InfoFlowAnalysis(self.p, self.m, minweight=8)
a._build_graph()
a._build_subgraph()
disconnected1 = self.p.lookup_type("disconnected1")
disconnected2 = self.p.lookup_type("disconnected2")
@ -342,12 +245,14 @@ class InfoFlowAnalysisTest(unittest.TestCase):
node8 = self.p.lookup_type("node8")
node9 = self.p.lookup_type("node9")
nodes = set(a.G.nodes_iter())
self.assertSetEqual(set([disconnected1, disconnected2, node1,
node2, node4, node5,
node6, node7, node8, node9]), nodes)
# don't test nodes, as disconnected nodes
# are not removed by subgraph generation
#nodes = set(a.subG.nodes_iter())
#self.assertSetEqual(set([disconnected1, disconnected2, node1,
# node2, node4, node5,
# node6, node7, node8, node9]), nodes)
edges = set(a.G.out_edges_iter())
edges = set(a.subG.out_edges_iter())
self.assertSetEqual(set([(disconnected1, disconnected2),
(disconnected2, disconnected1),
(node1, node2),
@ -357,84 +262,3 @@ class InfoFlowAnalysisTest(unittest.TestCase):
(node6, node7),
(node8, node9),
(node9, node8)]), edges)
r = a.G.edge[disconnected1][disconnected2]["rules"]
self.assertEqual(len(r), 1)
self.assertEqual(r[0].ruletype, "allow")
self.assertEqual(r[0].source, "disconnected1")
self.assertEqual(r[0].target, "disconnected2")
self.assertEqual(r[0].tclass, "infoflow2")
self.assertSetEqual(set(["super"]), r[0].perms)
self.assertRaises(RuleNotConditional, getattr, r[0], "conditional")
r = a.G.edge[disconnected2][disconnected1]["rules"]
self.assertEqual(len(r), 1)
self.assertEqual(r[0].ruletype, "allow")
self.assertEqual(r[0].source, "disconnected1")
self.assertEqual(r[0].target, "disconnected2")
self.assertEqual(r[0].tclass, "infoflow2")
self.assertSetEqual(set(["super"]), r[0].perms)
self.assertRaises(RuleNotConditional, getattr, r[0], "conditional")
r = sorted(a.G.edge[node1][node2]["rules"])
self.assertEqual(len(r), 1)
self.assertEqual(r[0].ruletype, "allow")
self.assertEqual(r[0].source, "node2")
self.assertEqual(r[0].target, "node1")
self.assertEqual(r[0].tclass, "infoflow")
self.assertSetEqual(set(["hi_r"]), r[0].perms)
self.assertRaises(RuleNotConditional, getattr, r[0], "conditional")
r = sorted(a.G.edge[node2][node4]["rules"])
self.assertEqual(len(r), 1)
self.assertEqual(r[0].ruletype, "allow")
self.assertEqual(r[0].source, "node2")
self.assertEqual(r[0].target, "node4")
self.assertEqual(r[0].tclass, "infoflow")
self.assertSetEqual(set(["hi_w"]), r[0].perms)
self.assertRaises(RuleNotConditional, getattr, r[0], "conditional")
r = sorted(a.G.edge[node4][node6]["rules"])
self.assertEqual(len(r), 1)
self.assertEqual(r[0].ruletype, "allow")
self.assertEqual(r[0].source, "node4")
self.assertEqual(r[0].target, "node6")
self.assertEqual(r[0].tclass, "infoflow2")
self.assertSetEqual(set(["hi_w"]), r[0].perms)
self.assertRaises(RuleNotConditional, getattr, r[0], "conditional")
r = sorted(a.G.edge[node5][node8]["rules"])
self.assertEqual(len(r), 1)
self.assertEqual(r[0].ruletype, "allow")
self.assertEqual(r[0].source, "node5")
self.assertEqual(r[0].target, "node8")
self.assertEqual(r[0].tclass, "infoflow2")
self.assertSetEqual(set(["hi_w"]), r[0].perms)
self.assertRaises(RuleNotConditional, getattr, r[0], "conditional")
r = sorted(a.G.edge[node6][node7]["rules"])
self.assertEqual(len(r), 1)
self.assertEqual(r[0].ruletype, "allow")
self.assertEqual(r[0].source, "node6")
self.assertEqual(r[0].target, "node7")
self.assertEqual(r[0].tclass, "infoflow")
self.assertSetEqual(set(["hi_w"]), r[0].perms)
self.assertRaises(RuleNotConditional, getattr, r[0], "conditional")
r = sorted(a.G.edge[node8][node9]["rules"])
self.assertEqual(len(r), 1)
self.assertEqual(r[0].ruletype, "allow")
self.assertEqual(r[0].source, "node8")
self.assertEqual(r[0].target, "node9")
self.assertEqual(r[0].tclass, "infoflow2")
self.assertSetEqual(set(["super"]), r[0].perms)
self.assertRaises(RuleNotConditional, getattr, r[0], "conditional")
r = sorted(a.G.edge[node9][node8]["rules"])
self.assertEqual(len(r), 1)
self.assertEqual(r[0].ruletype, "allow")
self.assertEqual(r[0].source, "node8")
self.assertEqual(r[0].target, "node9")
self.assertEqual(r[0].tclass, "infoflow2")
self.assertSetEqual(set(["super"]), r[0].perms)
self.assertRaises(RuleNotConditional, getattr, r[0], "conditional")