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