1# Authors: Karl MacMillan <[email protected]> 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, reference policy interfaces, and extended 54 permission access vector rules. By default only allow rules 55 are generated. The methods .set_gen_refpol, .set_gen_requires 56 and .set_gen_xperms turns on interface generation, 57 requires generation, and xperms rules generation respectively. 58 59 PolicyGenerator can also optionally add comments explaining 60 why a particular access was allowed based on the audit 61 messages that generated the access. The access vectors 62 passed in must have the .audit_msgs field set correctly 63 and .explain set to SHORT|LONG_EXPLANATION to enable this 64 feature. 65 66 The module created by PolicyGenerator can be passed to 67 output.ModuleWriter to output a text representation. 68 """ 69 def __init__(self, module=None): 70 """Initialize a PolicyGenerator with an optional 71 existing module. 72 73 If the module parameter is not None then access 74 will be added to the passed in module. Otherwise 75 a new reference policy module will be created. 76 """ 77 self.ifgen = None 78 self.explain = NO_EXPLANATION 79 self.gen_requires = False 80 if module: 81 self.module = module 82 else: 83 self.module = refpolicy.Module() 84 85 self.dontaudit = False 86 self.xperms = False 87 88 self.domains = None 89 self.gen_cil = False 90 self.comment_start = '#' 91 def set_gen_refpol(self, if_set=None, perm_maps=None): 92 """Set whether reference policy interfaces are generated. 93 94 To turn on interface generation pass in an interface set 95 to use for interface generation. To turn off interface 96 generation pass in None. 97 98 If interface generation is enabled requires generation 99 will also be enabled. 100 """ 101 if if_set: 102 self.ifgen = InterfaceGenerator(if_set, perm_maps) 103 self.gen_requires = True 104 else: 105 self.ifgen = None 106 self.__set_module_style() 107 108 109 def set_gen_requires(self, status=True): 110 """Set whether module requires are generated. 111 112 Passing in true will turn on requires generation and 113 False will disable generation. If requires generation is 114 disabled interface generation will also be disabled and 115 can only be re-enabled via .set_gen_refpol. 116 """ 117 self.gen_requires = status 118 119 def set_gen_explain(self, explain=SHORT_EXPLANATION): 120 """Set whether access is explained. 121 """ 122 self.explain = explain 123 124 def set_gen_dontaudit(self, dontaudit): 125 self.dontaudit = dontaudit 126 127 def set_gen_xperms(self, xperms): 128 """Set whether extended permission access vector rules 129 are generated. 130 """ 131 self.xperms = xperms 132 133 def set_gen_cil(self, gen_cil): 134 self.gen_cil = gen_cil 135 if gen_cil: 136 self.comment_start = ';' 137 else: 138 self.comment_start = '#' 139 140 def __set_module_style(self): 141 if self.ifgen: 142 refpolicy = True 143 else: 144 refpolicy = False 145 for mod in self.module.module_declarations(): 146 mod.refpolicy = refpolicy 147 148 def set_module_name(self, name, version="1.0"): 149 """Set the name of the module and optionally the version. 150 """ 151 # find an existing module declaration 152 m = None 153 for mod in self.module.module_declarations(): 154 m = mod 155 if not m: 156 m = refpolicy.ModuleDeclaration() 157 self.module.children.insert(0, m) 158 m.name = name 159 m.version = version 160 if self.ifgen: 161 m.refpolicy = True 162 else: 163 m.refpolicy = False 164 165 def get_module(self): 166 # Generate the requires 167 if self.gen_requires: 168 gen_requires(self.module) 169 170 """Return the generated module""" 171 return self.module 172 173 def __add_av_rule(self, av): 174 """Add access vector rule. 175 """ 176 rule = refpolicy.AVRule(av) 177 178 if self.dontaudit: 179 rule.rule_type = rule.DONTAUDIT 180 rule.comment = "" 181 if self.explain: 182 comment = refpolicy.Comment(explain_access(av, verbosity=self.explain)) 183 comment.set_gen_cil(self.gen_cil) 184 rule.comment = str(comment) 185 186 if av.type == audit2why.ALLOW: 187 rule.comment += "\n%s!!!! This avc is allowed in the current policy" % self.comment_start 188 189 if av.xperms: 190 rule.comment += "\n%s!!!! This av rule may have been overridden by an extended permission av rule" % self.comment_start 191 192 if av.type == audit2why.DONTAUDIT: 193 rule.comment += "\n%s!!!! This avc has a dontaudit rule in the current policy" % self.comment_start 194 195 if av.type == audit2why.BOOLEAN: 196 if len(av.data) > 1: 197 rule.comment += "\n%s!!!! This avc can be allowed using one of the these booleans:\n%s %s" % (self.comment_start, self.comment_start, ", ".join([x[0] for x in av.data])) 198 else: 199 rule.comment += "\n%s!!!! This avc can be allowed using the boolean '%s'" % (self.comment_start, av.data[0][0]) 200 201 if av.type == audit2why.CONSTRAINT: 202 rule.comment += "\n%s!!!! This avc is a constraint violation. You would need to modify the attributes of either the source or target types to allow this access." % self.comment_start 203 rule.comment += "\n%sConstraint rule: " % self.comment_start 204 rule.comment += "\n%s\t" % self.comment_start + av.data[0] 205 for reason in av.data[1:]: 206 rule.comment += "\n%s" % self.comment_start 207 rule.comment += "\tPossible cause is the source %s and target %s are different." % reason 208 209 try: 210 if ( av.type == audit2why.TERULE and 211 "write" in av.perms and 212 ( "dir" in av.obj_class or "open" in av.perms )): 213 if not self.domains: 214 self.domains = seinfo(ATTRIBUTE, name="domain")[0]["types"] 215 types=[] 216 217 for i in [x[TCONTEXT] for x in sesearch([ALLOW], {SCONTEXT: av.src_type, CLASS: av.obj_class, PERMS: av.perms})]: 218 if i not in self.domains: 219 types.append(i) 220 if len(types) == 1: 221 rule.comment += "\n%s!!!! The source type '%s' can write to a '%s' of the following type:\n%s %s\n" % (self.comment_start, av.src_type, av.obj_class, self.comment_start, ", ".join(types)) 222 elif len(types) >= 1: 223 rule.comment += "\n%s!!!! The source type '%s' can write to a '%s' of the following types:\n%s %s\n" % (self.comment_start, av.src_type, av.obj_class, self.comment_start, ", ".join(types)) 224 except: 225 pass 226 227 self.module.children.append(rule) 228 229 def __add_ext_av_rules(self, av): 230 """Add extended permission access vector rules. 231 """ 232 for op in av.xperms.keys(): 233 extrule = refpolicy.AVExtRule(av, op) 234 235 if self.dontaudit: 236 extrule.rule_type = extrule.DONTAUDITXPERM 237 238 self.module.children.append(extrule) 239 240 def add_access(self, av_set): 241 """Add the access from the access vector set to this 242 module. 243 """ 244 # Use the interface generator to split the access 245 # into raw allow rules and interfaces. After this 246 # a will contain a list of access that should be 247 # used as raw allow rules and the interfaces will 248 # be added to the module. 249 if self.ifgen: 250 raw_allow, ifcalls = self.ifgen.gen(av_set, self.explain) 251 self.module.children.extend(ifcalls) 252 else: 253 raw_allow = av_set 254 255 # Generate the raw allow rules from the filtered list 256 for av in raw_allow: 257 self.__add_av_rule(av) 258 if self.xperms and av.xperms: 259 self.__add_ext_av_rules(av) 260 261 def add_role_types(self, role_type_set): 262 for role_type in role_type_set: 263 self.module.children.append(role_type) 264 265def explain_access(av, ml=None, verbosity=SHORT_EXPLANATION): 266 """Explain why a policy statement was generated. 267 268 Return a string containing a text explanation of 269 why a policy statement was generated. The string is 270 commented and wrapped and can be directly inserted 271 into a policy. 272 273 Params: 274 av - access vector representing the access. Should 275 have .audit_msgs set appropriately. 276 verbosity - the amount of explanation provided. Should 277 be set to NO_EXPLANATION, SHORT_EXPLANATION, or 278 LONG_EXPLANATION. 279 Returns: 280 list of strings - strings explaining the access or an empty 281 string if verbosity=NO_EXPLANATION or there is not sufficient 282 information to provide an explanation. 283 """ 284 s = [] 285 286 def explain_interfaces(): 287 if not ml: 288 return 289 s.append(" Interface options:") 290 for match in ml.all(): 291 ifcall = call_interface(match.interface, ml.av) 292 s.append(' %s # [%d]' % (ifcall.to_string(), match.dist)) 293 294 295 # Format the raw audit data to explain why the 296 # access was requested - either long or short. 297 if verbosity == LONG_EXPLANATION: 298 for msg in av.audit_msgs: 299 s.append(' %s' % msg.header) 300 s.append(' scontext="%s" tcontext="%s"' % 301 (str(msg.scontext), str(msg.tcontext))) 302 s.append(' class="%s" perms="%s"' % 303 (msg.tclass, refpolicy.list_to_space_str(msg.accesses))) 304 s.append(' comm="%s" exe="%s" path="%s"' % (msg.comm, msg.exe, msg.path)) 305 s.extend(textwrap.wrap('message="' + msg.message + '"', 80, initial_indent=" ", 306 subsequent_indent=" ")) 307 explain_interfaces() 308 elif verbosity: 309 s.append(' src="%s" tgt="%s" class="%s", perms="%s"' % 310 (av.src_type, av.tgt_type, av.obj_class, av.perms.to_space_str())) 311 # For the short display we are only going to use the additional information 312 # from the first audit message. For the vast majority of cases this info 313 # will always be the same anyway. 314 if len(av.audit_msgs) > 0: 315 msg = av.audit_msgs[0] 316 s.append(' comm="%s" exe="%s" path="%s"' % (msg.comm, msg.exe, msg.path)) 317 explain_interfaces() 318 return s 319 320def call_interface(interface, av): 321 params = [] 322 args = [] 323 324 params.extend(interface.params.values()) 325 params.sort(key=lambda param: param.num, reverse=True) 326 327 ifcall = refpolicy.InterfaceCall() 328 ifcall.ifname = interface.name 329 330 for i in range(len(params)): 331 if params[i].type == refpolicy.SRC_TYPE: 332 ifcall.args.append(av.src_type) 333 elif params[i].type == refpolicy.TGT_TYPE: 334 ifcall.args.append(av.tgt_type) 335 elif params[i].type == refpolicy.OBJ_CLASS: 336 ifcall.args.append(av.obj_class) 337 else: 338 print(params[i].type) 339 assert 0 340 341 assert len(ifcall.args) > 0 342 343 return ifcall 344 345class InterfaceGenerator: 346 def __init__(self, ifs, perm_maps=None): 347 self.ifs = ifs 348 self.hack_check_ifs(ifs) 349 self.matcher = matching.AccessMatcher(perm_maps) 350 self.calls = [] 351 352 def hack_check_ifs(self, ifs): 353 # FIXME: Disable interfaces we can't call - this is a hack. 354 # Because we don't handle roles, multiple parameters, etc., 355 # etc., we must make certain we can actually use a returned 356 # interface. 357 for x in ifs.interfaces.values(): 358 params = [] 359 params.extend(x.params.values()) 360 params.sort(key=lambda param: param.num, reverse=True) 361 for i in range(len(params)): 362 # Check that the parameter position matches 363 # the number (e.g., $1 is the first arg). This 364 # will fail if the parser missed something. 365 if (i + 1) != params[i].num: 366 x.enabled = False 367 break 368 # Check that we can handle the param type (currently excludes 369 # roles. 370 if params[i].type not in [refpolicy.SRC_TYPE, refpolicy.TGT_TYPE, 371 refpolicy.OBJ_CLASS]: 372 x.enabled = False 373 break 374 375 def gen(self, avs, verbosity): 376 raw_av = self.match(avs) 377 ifcalls = [] 378 for ml in self.calls: 379 ifcall = call_interface(ml.best().interface, ml.av) 380 if verbosity: 381 ifcall.comment = refpolicy.Comment(explain_access(ml.av, ml, verbosity)) 382 ifcalls.append((ifcall, ml)) 383 384 d = [] 385 for ifcall, ifs in ifcalls: 386 found = False 387 for o_ifcall in d: 388 if o_ifcall.matches(ifcall): 389 if o_ifcall.comment and ifcall.comment: 390 o_ifcall.comment.merge(ifcall.comment) 391 found = True 392 if not found: 393 d.append(ifcall) 394 395 return (raw_av, d) 396 397 398 def match(self, avs): 399 raw_av = [] 400 for av in avs: 401 ans = matching.MatchList() 402 self.matcher.search_ifs(self.ifs, av, ans) 403 if len(ans): 404 self.calls.append(ans) 405 else: 406 raw_av.append(av) 407 408 return raw_av 409 410 411def gen_requires(module): 412 """Add require statements to the module. 413 """ 414 def collect_requires(node): 415 r = refpolicy.Require() 416 for avrule in node.avrules(): 417 r.types.update(avrule.src_types) 418 r.types.update(avrule.tgt_types) 419 for obj in avrule.obj_classes: 420 r.add_obj_class(obj, avrule.perms) 421 422 for ifcall in node.interface_calls(): 423 for arg in ifcall.args: 424 # FIXME - handle non-type arguments when we 425 # can actually figure those out. 426 r.types.add(arg) 427 428 for role_type in node.role_types(): 429 r.roles.add(role_type.role) 430 r.types.update(role_type.types) 431 432 r.types.discard("self") 433 434 node.children.insert(0, r) 435 436 # FUTURE - this is untested on modules with any sort of 437 # nesting 438 for node in module.nodes(): 439 collect_requires(node) 440 441 442