1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
|
#!/usr/bin/env python3
# Copyright 2014-2015, Tresys Technology, LLC
#
# SPDX-License-Identifier: GPL-2.0-only
#
import argparse
import sys
import logging
import signal
import warnings
from typing import Dict, Optional
import networkx as nx
import setools
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
parser = argparse.ArgumentParser(
description="SELinux policy information flow analysis tool.",
epilog="If no analysis is selected, all information flow out of the source will be printed.")
parser.add_argument("--full", help="Print full rule lists for information flows.",
action="store_true")
parser.add_argument("--version", action="version", version=setools.__version__)
parser.add_argument("--stats", action="store_true",
help="Display statistics at the end of the analysis.")
parser.add_argument("-v", "--verbose", action="store_true",
help="Print extra informational messages")
parser.add_argument("--debug", action="store_true", dest="debug", help="Enable debugging.")
settings = parser.add_argument_group("Analysis settings")
settings.add_argument("-p", "--policy",
help="Path to SELinux policy to analyze.")
settings.add_argument("-m", "--map",
help="Path to alternative permission map file.")
settings.add_argument("-s", "--source", required=True,
help="Source type of the analysis.")
settings.add_argument("-t", "--target", default="",
help="Target type of the analysis.")
alg = parser.add_argument_group("Analysis algorithm")
alg.add_argument("-S", "--shortest_path", action="store_true",
help="Calculate all shortest paths.")
alg.add_argument("-A", "--all_paths", type=int, metavar="MAX_STEPS",
help="Calculate all paths, with the specified maximum path length. (Expensive)")
opts = parser.add_argument_group("Analysis options")
opts.add_argument("-r", "--reverse", action="store_true",
help="Display information flows into the source type. "
"No effect if a target type is specified.")
opts.add_argument("-w", "--min_weight", default=3, type=int,
help="Minimum permission weight. Default is 3.")
opts.add_argument("-l", "--limit_flows", default=0, type=int,
help="Limit to the specified number of flows. Default is unlimited.")
opts.add_argument("-b", "--booleans", default=None,
help="Specify the boolean values to use."
" Options are default, or \"foo:true,bar:false...\"")
opts.add_argument("-o", "--output_file", help="Output file for graphical results, PNG format.")
opts.add_argument("exclude", nargs="*",
help="List of excluded types in the analysis.")
args = parser.parse_args()
if not args.target and (args.shortest_path or args.all_paths):
parser.error("The target type must be specified to determine a path.")
if args.target and not (args.shortest_path or args.all_paths):
parser.error("A target type is not used for flows in/out of a type.")
if args.limit_flows < 0:
parser.error("Limit on information flows cannot be negative.")
if args.debug:
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s|%(levelname)s|%(name)s|%(message)s')
if not sys.warnoptions:
warnings.simplefilter("default")
elif args.verbose:
logging.basicConfig(level=logging.INFO, format='%(message)s')
if not sys.warnoptions:
warnings.simplefilter("default")
else:
logging.basicConfig(level=logging.WARNING, format='%(message)s')
if not sys.warnoptions:
warnings.simplefilter("ignore")
booleans: Optional[Dict[str, bool]] = None
if args.booleans == 'default':
booleans = {}
elif args.booleans is not None:
booleans = {}
for boolean in args.booleans.split(','):
try:
key, value = boolean.split(':')
if value.lower() == 'true':
booleans[key] = True
elif value.lower() == 'false':
booleans[key] = False
else:
parser.error("Conditional value must be true or false.")
except ValueError:
parser.error("Expected boolean format foo:true,bar:false")
try:
p = setools.SELinuxPolicy(args.policy)
m = setools.PermissionMap(args.map)
g = setools.InfoFlowAnalysis(p, m, min_weight=args.min_weight, exclude=args.exclude,
booleans=booleans)
flownum: int = 0
flow: setools.InfoFlowPath
stepnum: int = 0
step: setools.InfoFlowStep
if args.shortest_path or args.all_paths:
g.source = args.source
g.target = args.target
if args.shortest_path:
g.mode = setools.InfoFlowAnalysis.Mode.ShortestPaths
else:
g.mode = setools.InfoFlowAnalysis.Mode.AllPaths
g.depth_limit = args.all_paths
if args.output_file:
pgv = nx.nx_agraph.to_agraph(g.graphical_results())
pgv.draw(path=args.output_file, prog="dot", format="png")
else:
for flownum, flow in enumerate(g.results(), start=1): # type: ignore
print(f"Flow {flownum}:")
for stepnum, step in enumerate(flow, start=1):
if args.full:
print(f" Step {stepnum}: {step:full}\n")
else:
print(f" Step {stepnum}: {step}")
if args.limit_flows and flownum >= args.limit_flows:
break
print(f"\n{flownum} information flow(s) found.")
else: # single step, direct info flow
if args.reverse:
g.mode = setools.InfoFlowAnalysis.Mode.FlowsIn
g.target = args.source
else:
g.mode = setools.InfoFlowAnalysis.Mode.FlowsOut
g.source = args.source
if args.output_file:
pgv = nx.nx_agraph.to_agraph(g.graphical_results())
pgv.draw(path=args.output_file, prog="dot", format="png")
else:
for flownum, step in enumerate(g.results(), start=1): # type: ignore
if args.full:
print(f"Flow {flownum}: {step:full}\n")
else:
print(f"Flow {flownum}: {step}")
if args.limit_flows and flownum >= args.limit_flows:
break
print(f"\n{flownum} information flow(s) found.")
if args.stats:
print("\nGraph statistics:")
print(g.get_stats())
except AssertionError:
# Always provide a traceback for assertion errors
raise
except Exception as err:
if args.debug:
raise
else:
print(err)
sys.exit(1)
|