1# Authors: Karl MacMillan <kmacmillan@mentalrootkit.com> 2# 3# Copyright (C) 2006 Red Hat 4# see file 'COPYING' for use and warranty information 5# 6# This program is free software; you can redistribute it and/or 7# modify it under the terms of the GNU General Public License as 8# published by the Free Software Foundation; version 2 only 9# 10# This program is distributed in the hope that it will be useful, 11# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13# GNU General Public License for more details. 14# 15# You should have received a copy of the GNU General Public License 16# along with this program; if not, write to the Free Software 17# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18# 19 20""" 21classes and algorithms for the generation of SELinux policy. 22""" 23 24import itertools 25import textwrap 26 27import selinux.audit2why as audit2why 28try: 29 from setools import * 30except: 31 pass 32 33from . import refpolicy 34from . import objectmodel 35from . import access 36from . import interfaces 37from . import matching 38from . import util 39# Constants for the level of explanation from the generation 40# routines 41NO_EXPLANATION = 0 42SHORT_EXPLANATION = 1 43LONG_EXPLANATION = 2 44 45class PolicyGenerator: 46 """Generate a reference policy module from access vectors. 47 48 PolicyGenerator generates a new reference policy module 49 or updates an existing module based on requested access 50 in the form of access vectors. 51 52 It generates allow rules and optionally module require 53 statements and reference policy interfaces. By default 54 only allow rules are generated. The methods .set_gen_refpol 55 and .set_gen_requires turns on interface generation and 56 requires generation respectively. 57 58 PolicyGenerator can also optionally add comments explaining 59 why a particular access was allowed based on the audit 60 messages that generated the access. The access vectors 61 passed in must have the .audit_msgs field set correctly 62 and .explain set to SHORT|LONG_EXPLANATION to enable this 63 feature. 64 65 The module created by PolicyGenerator can be passed to 66 output.ModuleWriter to output a text representation. 67 """ 68 def __init__(self, module=None): 69 """Initialize a PolicyGenerator with an optional 70 existing module. 71 72 If the module paramater is not None then access 73 will be added to the passed in module. Otherwise 74 a new reference policy module will be created. 75 """ 76 self.ifgen = None 77 self.explain = NO_EXPLANATION 78 self.gen_requires = False 79 if module: 80 self.moduel = module 81 else: 82 self.module = refpolicy.Module() 83 84 self.dontaudit = False 85 86 self.domains = None 87 def set_gen_refpol(self, if_set=None, perm_maps=None): 88 """Set whether reference policy interfaces are generated. 89 90 To turn on interface generation pass in an interface set 91 to use for interface generation. To turn off interface 92 generation pass in None. 93 94 If interface generation is enabled requires generation 95 will also be enabled. 96 """ 97 if if_set: 98 self.ifgen = InterfaceGenerator(if_set, perm_maps) 99 self.gen_requires = True 100 else: 101 self.ifgen = None 102 self.__set_module_style() 103 104 105 def set_gen_requires(self, status=True): 106 """Set whether module requires are generated. 107 108 Passing in true will turn on requires generation and 109 False will disable generation. If requires generation is 110 disabled interface generation will also be disabled and 111 can only be re-enabled via .set_gen_refpol. 112 """ 113 self.gen_requires = status 114 115 def set_gen_explain(self, explain=SHORT_EXPLANATION): 116 """Set whether access is explained. 117 """ 118 self.explain = explain 119 120 def set_gen_dontaudit(self, dontaudit): 121 self.dontaudit = dontaudit 122 123 def __set_module_style(self): 124 if self.ifgen: 125 refpolicy = True 126 else: 127 refpolicy = False 128 for mod in self.module.module_declarations(): 129 mod.refpolicy = refpolicy 130 131 def set_module_name(self, name, version="1.0"): 132 """Set the name of the module and optionally the version. 133 """ 134 # find an existing module declaration 135 m = None 136 for mod in self.module.module_declarations(): 137 m = mod 138 if not m: 139 m = refpolicy.ModuleDeclaration() 140 self.module.children.insert(0, m) 141 m.name = name 142 m.version = version 143 if self.ifgen: 144 m.refpolicy = True 145 else: 146 m.refpolicy = False 147 148 def get_module(self): 149 # Generate the requires 150 if self.gen_requires: 151 gen_requires(self.module) 152 153 """Return the generated module""" 154 return self.module 155 156 def __add_allow_rules(self, avs): 157 for av in avs: 158 rule = refpolicy.AVRule(av) 159 if self.dontaudit: 160 rule.rule_type = rule.DONTAUDIT 161 rule.comment = "" 162 if self.explain: 163 rule.comment = str(refpolicy.Comment(explain_access(av, verbosity=self.explain))) 164 if av.type == audit2why.ALLOW: 165 rule.comment += "\n#!!!! This avc is allowed in the current policy" 166 if av.type == audit2why.DONTAUDIT: 167 rule.comment += "\n#!!!! This avc has a dontaudit rule in the current policy" 168 169 if av.type == audit2why.BOOLEAN: 170 if len(av.data) > 1: 171 rule.comment += "\n#!!!! This avc can be allowed using one of the these booleans:\n# %s" % ", ".join([x[0] for x in av.data]) 172 else: 173 rule.comment += "\n#!!!! This avc can be allowed using the boolean '%s'" % av.data[0][0] 174 175 if av.type == audit2why.CONSTRAINT: 176 rule.comment += "\n#!!!! This avc is a constraint violation. You would need to modify the attributes of either the source or target types to allow this access." 177 rule.comment += "\n#Constraint rule: " 178 rule.comment += "\n#\t" + av.data[0] 179 for reason in av.data[1:]: 180 rule.comment += "\n#\tPossible cause is the source %s and target %s are different." % reason 181 182 try: 183 if ( av.type == audit2why.TERULE and 184 "write" in av.perms and 185 ( "dir" in av.obj_class or "open" in av.perms )): 186 if not self.domains: 187 self.domains = seinfo(ATTRIBUTE, name="domain")[0]["types"] 188 types=[] 189 190 for i in [x[TCONTEXT] for x in sesearch([ALLOW], {SCONTEXT: av.src_type, CLASS: av.obj_class, PERMS: av.perms})]: 191 if i not in self.domains: 192 types.append(i) 193 if len(types) == 1: 194 rule.comment += "\n#!!!! The source type '%s' can write to a '%s' of the following type:\n# %s\n" % ( av.src_type, av.obj_class, ", ".join(types)) 195 elif len(types) >= 1: 196 rule.comment += "\n#!!!! The source type '%s' can write to a '%s' of the following types:\n# %s\n" % ( av.src_type, av.obj_class, ", ".join(types)) 197 except: 198 pass 199 self.module.children.append(rule) 200 201 202 def add_access(self, av_set): 203 """Add the access from the access vector set to this 204 module. 205 """ 206 # Use the interface generator to split the access 207 # into raw allow rules and interfaces. After this 208 # a will contain a list of access that should be 209 # used as raw allow rules and the interfaces will 210 # be added to the module. 211 if self.ifgen: 212 raw_allow, ifcalls = self.ifgen.gen(av_set, self.explain) 213 self.module.children.extend(ifcalls) 214 else: 215 raw_allow = av_set 216 217 # Generate the raw allow rules from the filtered list 218 self.__add_allow_rules(raw_allow) 219 220 def add_role_types(self, role_type_set): 221 for role_type in role_type_set: 222 self.module.children.append(role_type) 223 224def explain_access(av, ml=None, verbosity=SHORT_EXPLANATION): 225 """Explain why a policy statement was generated. 226 227 Return a string containing a text explanation of 228 why a policy statement was generated. The string is 229 commented and wrapped and can be directly inserted 230 into a policy. 231 232 Params: 233 av - access vector representing the access. Should 234 have .audit_msgs set appropriately. 235 verbosity - the amount of explanation provided. Should 236 be set to NO_EXPLANATION, SHORT_EXPLANATION, or 237 LONG_EXPLANATION. 238 Returns: 239 list of strings - strings explaining the access or an empty 240 string if verbosity=NO_EXPLANATION or there is not sufficient 241 information to provide an explanation. 242 """ 243 s = [] 244 245 def explain_interfaces(): 246 if not ml: 247 return 248 s.append(" Interface options:") 249 for match in ml.all(): 250 ifcall = call_interface(match.interface, ml.av) 251 s.append(' %s # [%d]' % (ifcall.to_string(), match.dist)) 252 253 254 # Format the raw audit data to explain why the 255 # access was requested - either long or short. 256 if verbosity == LONG_EXPLANATION: 257 for msg in av.audit_msgs: 258 s.append(' %s' % msg.header) 259 s.append(' scontext="%s" tcontext="%s"' % 260 (str(msg.scontext), str(msg.tcontext))) 261 s.append(' class="%s" perms="%s"' % 262 (msg.tclass, refpolicy.list_to_space_str(msg.accesses))) 263 s.append(' comm="%s" exe="%s" path="%s"' % (msg.comm, msg.exe, msg.path)) 264 s.extend(textwrap.wrap('message="' + msg.message + '"', 80, initial_indent=" ", 265 subsequent_indent=" ")) 266 explain_interfaces() 267 elif verbosity: 268 s.append(' src="%s" tgt="%s" class="%s", perms="%s"' % 269 (av.src_type, av.tgt_type, av.obj_class, av.perms.to_space_str())) 270 # For the short display we are only going to use the additional information 271 # from the first audit message. For the vast majority of cases this info 272 # will always be the same anyway. 273 if len(av.audit_msgs) > 0: 274 msg = av.audit_msgs[0] 275 s.append(' comm="%s" exe="%s" path="%s"' % (msg.comm, msg.exe, msg.path)) 276 explain_interfaces() 277 return s 278 279def call_interface(interface, av): 280 params = [] 281 args = [] 282 283 params.extend(interface.params.values()) 284 params.sort(key=lambda param: param.num, reverse=True) 285 286 ifcall = refpolicy.InterfaceCall() 287 ifcall.ifname = interface.name 288 289 for i in range(len(params)): 290 if params[i].type == refpolicy.SRC_TYPE: 291 ifcall.args.append(av.src_type) 292 elif params[i].type == refpolicy.TGT_TYPE: 293 ifcall.args.append(av.tgt_type) 294 elif params[i].type == refpolicy.OBJ_CLASS: 295 ifcall.args.append(av.obj_class) 296 else: 297 print(params[i].type) 298 assert(0) 299 300 assert(len(ifcall.args) > 0) 301 302 return ifcall 303 304class InterfaceGenerator: 305 def __init__(self, ifs, perm_maps=None): 306 self.ifs = ifs 307 self.hack_check_ifs(ifs) 308 self.matcher = matching.AccessMatcher(perm_maps) 309 self.calls = [] 310 311 def hack_check_ifs(self, ifs): 312 # FIXME: Disable interfaces we can't call - this is a hack. 313 # Because we don't handle roles, multiple paramaters, etc., 314 # etc., we must make certain we can actually use a returned 315 # interface. 316 for x in ifs.interfaces.values(): 317 params = [] 318 params.extend(x.params.values()) 319 params.sort(key=lambda param: param.num, reverse=True) 320 for i in range(len(params)): 321 # Check that the paramater position matches 322 # the number (e.g., $1 is the first arg). This 323 # will fail if the parser missed something. 324 if (i + 1) != params[i].num: 325 x.enabled = False 326 break 327 # Check that we can handle the param type (currently excludes 328 # roles. 329 if params[i].type not in [refpolicy.SRC_TYPE, refpolicy.TGT_TYPE, 330 refpolicy.OBJ_CLASS]: 331 x.enabled = False 332 break 333 334 def gen(self, avs, verbosity): 335 raw_av = self.match(avs) 336 ifcalls = [] 337 for ml in self.calls: 338 ifcall = call_interface(ml.best().interface, ml.av) 339 if verbosity: 340 ifcall.comment = refpolicy.Comment(explain_access(ml.av, ml, verbosity)) 341 ifcalls.append((ifcall, ml)) 342 343 d = [] 344 for ifcall, ifs in ifcalls: 345 found = False 346 for o_ifcall in d: 347 if o_ifcall.matches(ifcall): 348 if o_ifcall.comment and ifcall.comment: 349 o_ifcall.comment.merge(ifcall.comment) 350 found = True 351 if not found: 352 d.append(ifcall) 353 354 return (raw_av, d) 355 356 357 def match(self, avs): 358 raw_av = [] 359 for av in avs: 360 ans = matching.MatchList() 361 self.matcher.search_ifs(self.ifs, av, ans) 362 if len(ans): 363 self.calls.append(ans) 364 else: 365 raw_av.append(av) 366 367 return raw_av 368 369 370def gen_requires(module): 371 """Add require statements to the module. 372 """ 373 def collect_requires(node): 374 r = refpolicy.Require() 375 for avrule in node.avrules(): 376 r.types.update(avrule.src_types) 377 r.types.update(avrule.tgt_types) 378 for obj in avrule.obj_classes: 379 r.add_obj_class(obj, avrule.perms) 380 381 for ifcall in node.interface_calls(): 382 for arg in ifcall.args: 383 # FIXME - handle non-type arguments when we 384 # can actually figure those out. 385 r.types.add(arg) 386 387 for role_type in node.role_types(): 388 r.roles.add(role_type.role) 389 r.types.update(role_type.types) 390 391 r.types.discard("self") 392 393 node.children.insert(0, r) 394 395 # FUTURE - this is untested on modules with any sort of 396 # nesting 397 for node in module.nodes(): 398 collect_requires(node) 399 400 401