mirror of
https://github.com/SELinuxProject/setools
synced 2025-02-21 22:46:50 +00:00
DTA: implement excluded types and reverse analysis.
closes #25 closes #26
This commit is contained in:
parent
094dbe3906
commit
da44b3592b
11
sedta
11
sedta
@ -1,5 +1,5 @@
|
||||
#!/usr/bin/python
|
||||
# Copyright 2014, Tresys Technology, LLC
|
||||
# Copyright 2014-2015, Tresys Technology, LLC
|
||||
#
|
||||
# This file is part of SETools.
|
||||
#
|
||||
@ -84,6 +84,12 @@ alg.add_argument("-S", "--shortest_path", action="store_true",
|
||||
alg.add_argument("-A", "--all_paths",
|
||||
help="Calculate all paths, with the specified maximum path length. (Expensive)", type=int, metavar="MAX_STEPS")
|
||||
|
||||
opts = parser.add_argument_group("Analysis options")
|
||||
opts.add_argument("-r", "--reverse", action="store_true", default=False,
|
||||
help="Perform a reverse DTA.")
|
||||
opts.add_argument(
|
||||
"exclude", help="List of excluded types in the analysis.", nargs="*")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.target and (args.shortest_path or args.all_paths):
|
||||
@ -94,7 +100,7 @@ if args.target and not (args.shortest_path or args.all_paths):
|
||||
|
||||
try:
|
||||
p = setools.SELinuxPolicy(args.policy)
|
||||
g = setools.dta.DomainTransitionAnalysis(p)
|
||||
g = setools.dta.DomainTransitionAnalysis(p, reverse=args.reverse, exclude=args.exclude)
|
||||
except Exception as err:
|
||||
print(err)
|
||||
sys.exit(1)
|
||||
@ -131,7 +137,6 @@ else: # single transition
|
||||
print("Transition {0}: {1} -> {2}\n".format(i, src, tgt))
|
||||
print_transition(trans, entrypoints, setexec, dyntrans, setcur)
|
||||
|
||||
|
||||
print(i, "domain transition(s) found.")
|
||||
|
||||
if args.stats:
|
||||
|
163
setools/dta.py
163
setools/dta.py
@ -1,4 +1,4 @@
|
||||
# Copyright 2014, Tresys Technology, LLC
|
||||
# Copyright 2014-2015, Tresys Technology, LLC
|
||||
#
|
||||
# This file is part of SETools.
|
||||
#
|
||||
@ -26,13 +26,16 @@ class DomainTransitionAnalysis(object):
|
||||
|
||||
"""Domain transition analysis."""
|
||||
|
||||
def __init__(self, policy):
|
||||
def __init__(self, policy, reverse=False, exclude=[]):
|
||||
"""
|
||||
Parameter:
|
||||
policy The policy to analyze.
|
||||
"""
|
||||
self.policy = policy
|
||||
self.set_exclude(exclude)
|
||||
self.set_reverse(reverse)
|
||||
self.rebuildgraph = True
|
||||
self.rebuildsubgraph = True
|
||||
self.G = nx.DiGraph()
|
||||
|
||||
def __get_entrypoints(self, source, target):
|
||||
@ -51,24 +54,18 @@ class DomainTransitionAnalysis(object):
|
||||
exec The execute rules.
|
||||
trans The type_transition rules.
|
||||
"""
|
||||
for e in self.G.edge[source][target]['entrypoint']:
|
||||
if self.G.edge[source][target]['type_transition'][e]:
|
||||
for e in self.subG.edge[source][target]['entrypoint']:
|
||||
if self.subG.edge[source][target]['type_transition'][e]:
|
||||
yield e, \
|
||||
self.G.edge[source][target]['entrypoint'][e], \
|
||||
self.G.edge[source][target]['execute'][e], \
|
||||
self.G.edge[source][target]['type_transition'][e]
|
||||
self.subG.edge[source][target]['entrypoint'][e], \
|
||||
self.subG.edge[source][target]['execute'][e], \
|
||||
self.subG.edge[source][target]['type_transition'][e]
|
||||
else:
|
||||
yield e, \
|
||||
self.G.edge[source][target]['entrypoint'][e], \
|
||||
self.G.edge[source][target]['execute'][e], \
|
||||
self.subG.edge[source][target]['entrypoint'][e], \
|
||||
self.subG.edge[source][target]['execute'][e], \
|
||||
[]
|
||||
|
||||
# TODO: consider making sure source and target are valid
|
||||
# both as types and also in graph
|
||||
# TODO: make reverse an option. on that option,
|
||||
# simply reverse the graph. Will probably have to fix up
|
||||
# __get_steps to output correctly so sedta doesn't have to
|
||||
# change.
|
||||
def __get_steps(self, path):
|
||||
"""
|
||||
Generator which returns the source, target, and associated rules
|
||||
@ -93,12 +90,43 @@ class DomainTransitionAnalysis(object):
|
||||
source = path[s - 1]
|
||||
target = path[s]
|
||||
|
||||
yield source, target, \
|
||||
self.G.edge[source][target]['transition'], \
|
||||
self.__get_entrypoints(source, target), \
|
||||
self.G.edge[source][target]['setexec'], \
|
||||
self.G.edge[source][target]['dyntransition'], \
|
||||
self.G.edge[source][target]['setcurrent']
|
||||
if self.reverse:
|
||||
real_source, real_target = target, source
|
||||
else:
|
||||
real_source, real_target = source, target
|
||||
|
||||
# It seems that NetworkX does not reverse the dictionaries
|
||||
# that store the attributes, so real_* is used everywhere
|
||||
# below, rather than just the first line.
|
||||
yield real_source, real_target, \
|
||||
self.subG.edge[real_source][real_target]['transition'], \
|
||||
self.__get_entrypoints(real_source, real_target), \
|
||||
self.subG.edge[real_source][real_target]['setexec'], \
|
||||
self.subG.edge[real_source][real_target]['dyntransition'], \
|
||||
self.subG.edge[real_source][real_target]['setcurrent']
|
||||
|
||||
def set_reverse(self, reverse):
|
||||
"""
|
||||
Set forward/reverse DTA direction.
|
||||
|
||||
Parameter:
|
||||
reverse If true, a reverse DTA is performed, otherwise a
|
||||
forward DTA is performed.
|
||||
"""
|
||||
|
||||
self.reverse = bool(reverse)
|
||||
self.rebuildsubgraph = True
|
||||
|
||||
def set_exclude(self, exclude):
|
||||
"""
|
||||
Set the domains to exclude from the domain transition analysis.
|
||||
|
||||
Parameter:
|
||||
exclude A list of types.
|
||||
"""
|
||||
|
||||
self.exclude = [self.policy.lookup_type(t) for t in exclude]
|
||||
self.rebuildsubgraph = True
|
||||
|
||||
def shortest_path(self, source, target):
|
||||
"""
|
||||
@ -118,12 +146,12 @@ class DomainTransitionAnalysis(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:
|
||||
@ -149,12 +177,12 @@ class DomainTransitionAnalysis(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:
|
||||
@ -179,12 +207,12 @@ class DomainTransitionAnalysis(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:
|
||||
@ -207,16 +235,24 @@ class DomainTransitionAnalysis(object):
|
||||
"""
|
||||
s = self.policy.lookup_type(type_)
|
||||
|
||||
if self.rebuildgraph:
|
||||
self._build_graph()
|
||||
if self.rebuildsubgraph:
|
||||
self._build_subgraph()
|
||||
|
||||
for source, target in self.G.out_edges_iter(s):
|
||||
yield source, target, \
|
||||
self.G.edge[source][target]['transition'], \
|
||||
self.__get_entrypoints(source, target), \
|
||||
self.G.edge[source][target]['setexec'], \
|
||||
self.G.edge[source][target]['dyntransition'], \
|
||||
self.G.edge[source][target]['setcurrent']
|
||||
for source, target in self.subG.out_edges_iter(s):
|
||||
if self.reverse:
|
||||
real_source, real_target = target, source
|
||||
else:
|
||||
real_source, real_target = source, target
|
||||
|
||||
# It seems that NetworkX does not reverse the dictionaries
|
||||
# that store the attributes, so real_* is used everywhere
|
||||
# below, rather than just the first line.
|
||||
yield real_source, real_target, \
|
||||
self.subG.edge[real_source][real_target]['transition'], \
|
||||
self.__get_entrypoints(real_source, real_target), \
|
||||
self.subG.edge[real_source][real_target]['setexec'], \
|
||||
self.subG.edge[real_source][real_target]['dyntransition'], \
|
||||
self.subG.edge[real_source][real_target]['setcurrent']
|
||||
|
||||
def get_stats(self):
|
||||
"""
|
||||
@ -442,3 +478,46 @@ class DomainTransitionAnalysis(object):
|
||||
del self.G[s][t]['setcurrent'][:]
|
||||
|
||||
self.rebuildgraph = False
|
||||
self.rebuildsubgraph = True
|
||||
|
||||
def _build_subgraph(self):
|
||||
if self.rebuildgraph:
|
||||
self._build_graph()
|
||||
|
||||
# delete excluded domains from subgraph
|
||||
nodes = [n for n in self.G.nodes() if n not in self.exclude]
|
||||
# subgraph created this way to get copies of the edge
|
||||
# attributes. otherwise the edge attributes point to the
|
||||
# original graph, and the entrypoint removal below would also
|
||||
# affect the main graph.
|
||||
self.subG = nx.DiGraph(self.G.subgraph(nodes))
|
||||
|
||||
# delete excluded entrypoints from subgraph
|
||||
invalid_edge = []
|
||||
for source, target in self.subG.edges_iter():
|
||||
# can't change a dictionary that you're iterating over
|
||||
entrypoints = list(self.subG.edge[source][target]['entrypoint'])
|
||||
|
||||
for e in entrypoints:
|
||||
# clear the entrypoint data
|
||||
if e in self.exclude:
|
||||
del self.subG.edge[source][target]['entrypoint'][e]
|
||||
del self.subG.edge[source][target]['execute'][e]
|
||||
|
||||
try:
|
||||
del self.subG.edge[source][
|
||||
target]['type_transition'][e]
|
||||
except KeyError: # setexec
|
||||
pass
|
||||
|
||||
# cannot change the edges while iterating over them
|
||||
if len(self.subG.edge[source][target]['entrypoint']) == 0 and len(self.subG.edge[source][target]['dyntransition']) == 0:
|
||||
invalid_edge.append((source, target))
|
||||
|
||||
self.subG.remove_edges_from(invalid_edge)
|
||||
|
||||
# reverse graph for reverse DTA
|
||||
if self.reverse:
|
||||
self.subG.reverse(copy=False)
|
||||
|
||||
self.rebuildsubgraph = False
|
||||
|
161
tests/dta.py
161
tests/dta.py
@ -1,4 +1,4 @@
|
||||
# Copyright 2014, Tresys Technology, LLC
|
||||
# Copyright 2014-2015, Tresys Technology, LLC
|
||||
#
|
||||
# This file is part of SETools.
|
||||
#
|
||||
@ -477,3 +477,162 @@ class InfoFlowAnalysisTest(unittest.TestCase):
|
||||
# setcurrent
|
||||
r = self.a.G.edge[s][t]["setcurrent"]
|
||||
self.assertEqual(len(r), 0)
|
||||
|
||||
def test_100_forward_subgraph_structure(self):
|
||||
"""DTA: verify forward subgraph structure."""
|
||||
# The purpose is to ensure the subgraph is reversed
|
||||
# only when the reverse option is set, not that
|
||||
# graph reversal is correct (assumed that NetworkX
|
||||
# does it correctly).
|
||||
# Don't check node list since the disconnected nodes are not
|
||||
# removed after removing invalid domain transitions
|
||||
|
||||
self.a.set_reverse(False)
|
||||
self.a._build_subgraph()
|
||||
|
||||
start = self.p.lookup_type("start")
|
||||
trans1 = self.p.lookup_type("trans1")
|
||||
trans2 = self.p.lookup_type("trans2")
|
||||
trans3 = self.p.lookup_type("trans3")
|
||||
trans4 = self.p.lookup_type("trans4")
|
||||
trans5 = self.p.lookup_type("trans5")
|
||||
dyntrans100 = self.p.lookup_type("dyntrans100")
|
||||
bothtrans200 = self.p.lookup_type("bothtrans200")
|
||||
|
||||
edges = set(self.a.subG.out_edges_iter())
|
||||
self.assertSetEqual(set([(dyntrans100, bothtrans200),
|
||||
(start, dyntrans100),
|
||||
(start, trans1),
|
||||
(trans1, trans2),
|
||||
(trans2, trans3),
|
||||
(trans3, trans5)]), edges)
|
||||
|
||||
def test_101_reverse_subgraph_structure(self):
|
||||
"""DTA: verify reverse subgraph structure."""
|
||||
# The purpose is to ensure the subgraph is reversed
|
||||
# only when the reverse option is set, not that
|
||||
# graph reversal is correct (assumed that NetworkX
|
||||
# does it correctly).
|
||||
# Don't check node list since the disconnected nodes are not
|
||||
# removed after removing invalid domain transitions
|
||||
|
||||
self.a.set_reverse(True)
|
||||
self.a._build_subgraph()
|
||||
|
||||
start = self.p.lookup_type("start")
|
||||
trans1 = self.p.lookup_type("trans1")
|
||||
trans2 = self.p.lookup_type("trans2")
|
||||
trans3 = self.p.lookup_type("trans3")
|
||||
trans4 = self.p.lookup_type("trans4")
|
||||
trans5 = self.p.lookup_type("trans5")
|
||||
dyntrans100 = self.p.lookup_type("dyntrans100")
|
||||
bothtrans200 = self.p.lookup_type("bothtrans200")
|
||||
|
||||
edges = set(self.a.subG.out_edges_iter())
|
||||
self.assertSetEqual(set([(bothtrans200, dyntrans100),
|
||||
(dyntrans100, start),
|
||||
(trans1, start),
|
||||
(trans2, trans1),
|
||||
(trans3, trans2),
|
||||
(trans5, trans3)]), edges)
|
||||
|
||||
def test_200_exclude_domain(self):
|
||||
"""DTA: exclude domain type."""
|
||||
# Don't check node list since the disconnected nodes are not
|
||||
# removed after removing invalid domain transitions
|
||||
|
||||
self.a.set_reverse(False)
|
||||
self.a.set_exclude(["trans1"])
|
||||
self.a._build_subgraph()
|
||||
|
||||
start = self.p.lookup_type("start")
|
||||
trans1 = self.p.lookup_type("trans1")
|
||||
trans2 = self.p.lookup_type("trans2")
|
||||
trans3 = self.p.lookup_type("trans3")
|
||||
trans4 = self.p.lookup_type("trans4")
|
||||
trans5 = self.p.lookup_type("trans5")
|
||||
dyntrans100 = self.p.lookup_type("dyntrans100")
|
||||
bothtrans200 = self.p.lookup_type("bothtrans200")
|
||||
|
||||
edges = set(self.a.subG.out_edges_iter())
|
||||
self.assertSetEqual(set([(dyntrans100, bothtrans200),
|
||||
(start, dyntrans100),
|
||||
(trans2, trans3),
|
||||
(trans3, trans5)]), edges)
|
||||
|
||||
def test_201_exclude_entryoint_with_2entrypoints(self):
|
||||
"""DTA: exclude entrypoint type without transition deletion (other entrypoints)."""
|
||||
# Don't check node list since the disconnected nodes are not
|
||||
# removed after removing invalid domain transitions
|
||||
|
||||
self.a.set_reverse(False)
|
||||
self.a.set_exclude(["trans3_exec1"])
|
||||
self.a._build_subgraph()
|
||||
|
||||
start = self.p.lookup_type("start")
|
||||
trans1 = self.p.lookup_type("trans1")
|
||||
trans2 = self.p.lookup_type("trans2")
|
||||
trans3 = self.p.lookup_type("trans3")
|
||||
trans4 = self.p.lookup_type("trans4")
|
||||
trans5 = self.p.lookup_type("trans5")
|
||||
dyntrans100 = self.p.lookup_type("dyntrans100")
|
||||
bothtrans200 = self.p.lookup_type("bothtrans200")
|
||||
|
||||
edges = set(self.a.subG.out_edges_iter())
|
||||
self.assertSetEqual(set([(dyntrans100, bothtrans200),
|
||||
(start, dyntrans100),
|
||||
(start, trans1),
|
||||
(trans1, trans2),
|
||||
(trans2, trans3),
|
||||
(trans3, trans5)]), edges)
|
||||
|
||||
def test_202_exclude_entryoint_with_dyntrans(self):
|
||||
"""DTA: exclude entrypoint type without transition deletion (dyntrans)."""
|
||||
# Don't check node list since the disconnected nodes are not
|
||||
# removed after removing invalid domain transitions
|
||||
|
||||
self.a.set_reverse(False)
|
||||
self.a.set_exclude(["bothtrans200_exec"])
|
||||
self.a._build_subgraph()
|
||||
|
||||
start = self.p.lookup_type("start")
|
||||
trans1 = self.p.lookup_type("trans1")
|
||||
trans2 = self.p.lookup_type("trans2")
|
||||
trans3 = self.p.lookup_type("trans3")
|
||||
trans4 = self.p.lookup_type("trans4")
|
||||
trans5 = self.p.lookup_type("trans5")
|
||||
dyntrans100 = self.p.lookup_type("dyntrans100")
|
||||
bothtrans200 = self.p.lookup_type("bothtrans200")
|
||||
|
||||
edges = set(self.a.subG.out_edges_iter())
|
||||
self.assertSetEqual(set([(dyntrans100, bothtrans200),
|
||||
(start, dyntrans100),
|
||||
(start, trans1),
|
||||
(trans1, trans2),
|
||||
(trans2, trans3),
|
||||
(trans3, trans5)]), edges)
|
||||
|
||||
def test_203_exclude_entryoint_delete_transition(self):
|
||||
"""DTA: exclude entrypoint type with transition deletion."""
|
||||
# Don't check node list since the disconnected nodes are not
|
||||
# removed after removing invalid domain transitions
|
||||
|
||||
self.a.set_reverse(False)
|
||||
self.a.set_exclude(["trans2_exec"])
|
||||
self.a._build_subgraph()
|
||||
|
||||
start = self.p.lookup_type("start")
|
||||
trans1 = self.p.lookup_type("trans1")
|
||||
trans2 = self.p.lookup_type("trans2")
|
||||
trans3 = self.p.lookup_type("trans3")
|
||||
trans4 = self.p.lookup_type("trans4")
|
||||
trans5 = self.p.lookup_type("trans5")
|
||||
dyntrans100 = self.p.lookup_type("dyntrans100")
|
||||
bothtrans200 = self.p.lookup_type("bothtrans200")
|
||||
|
||||
edges = set(self.a.subG.out_edges_iter())
|
||||
self.assertSetEqual(set([(dyntrans100, bothtrans200),
|
||||
(start, dyntrans100),
|
||||
(start, trans1),
|
||||
(trans2, trans3),
|
||||
(trans3, trans5)]), edges)
|
||||
|
Loading…
Reference in New Issue
Block a user