Jeff Vander Stoep | 74e4f93 | 2016-02-08 15:27:10 -0800 | [diff] [blame^] | 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 | """ |
| 21 | classes and algorithms for the generation of SELinux policy. |
| 22 | """ |
| 23 | |
| 24 | import itertools |
| 25 | import textwrap |
| 26 | |
| 27 | import selinux.audit2why as audit2why |
| 28 | try: |
| 29 | from setools import * |
| 30 | except: |
| 31 | pass |
| 32 | |
| 33 | from . import refpolicy |
| 34 | from . import objectmodel |
| 35 | from . import access |
| 36 | from . import interfaces |
| 37 | from . import matching |
| 38 | from . import util |
| 39 | # Constants for the level of explanation from the generation |
| 40 | # routines |
| 41 | NO_EXPLANATION = 0 |
| 42 | SHORT_EXPLANATION = 1 |
| 43 | LONG_EXPLANATION = 2 |
| 44 | |
| 45 | class 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 | |
| 224 | def 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 | |
| 279 | def 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 | |
| 304 | class 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 | |
| 370 | def 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 | |