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