1#!/usr/bin/python3 -Es 2# Authors: Karl MacMillan <kmacmillan@mentalrootkit.com> 3# Authors: Dan Walsh <dwalsh@redhat.com> 4# 5# Copyright (C) 2006-2013 Red Hat 6# see file 'COPYING' for use and warranty information 7# 8# This program is free software; you can redistribute it and/or 9# modify it under the terms of the GNU General Public License as 10# published by the Free Software Foundation; version 2 only 11# 12# This program is distributed in the hope that it will be useful, 13# but WITHOUT ANY WARRANTY; without even the implied warranty of 14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15# GNU General Public License for more details. 16# 17# You should have received a copy of the GNU General Public License 18# along with this program; if not, write to the Free Software 19# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20# 21 22import sys 23import os 24 25import sepolgen.audit as audit 26import sepolgen.policygen as policygen 27import sepolgen.interfaces as interfaces 28import sepolgen.output as output 29import sepolgen.objectmodel as objectmodel 30import sepolgen.defaults as defaults 31import sepolgen.module as module 32from sepolgen.sepolgeni18n import _ 33import selinux.audit2why as audit2why 34import locale 35try: 36 locale.setlocale(locale.LC_ALL, '') 37except: 38 pass 39 40 41class AuditToPolicy: 42 VERSION = "%prog .1" 43 SYSLOG = "/var/log/messages" 44 45 def __init__(self): 46 self.__options = None 47 self.__parser = None 48 self.__avs = None 49 50 def __parse_options(self): 51 from optparse import OptionParser 52 53 parser = OptionParser(version=self.VERSION) 54 parser.add_option("-b", "--boot", action="store_true", dest="boot", default=False, 55 help="audit messages since last boot conflicts with -i") 56 parser.add_option("-a", "--all", action="store_true", dest="audit", default=False, 57 help="read input from audit log - conflicts with -i") 58 parser.add_option("-p", "--policy", dest="policy", default=None, help="Policy file to use for analysis") 59 parser.add_option("-d", "--dmesg", action="store_true", dest="dmesg", default=False, 60 help="read input from dmesg - conflicts with --all and --input") 61 parser.add_option("-i", "--input", dest="input", 62 help="read input from <input> - conflicts with -a") 63 parser.add_option("-l", "--lastreload", action="store_true", dest="lastreload", default=False, 64 help="read input only after the last reload") 65 parser.add_option("-r", "--requires", action="store_true", dest="requires", default=False, 66 help="generate require statements for rules") 67 parser.add_option("-m", "--module", dest="module", 68 help="set the module name - implies --requires") 69 parser.add_option("-M", "--module-package", dest="module_package", 70 help="generate a module package - conflicts with -o and -m") 71 parser.add_option("-o", "--output", dest="output", 72 help="append output to <filename>, conflicts with -M") 73 parser.add_option("-D", "--dontaudit", action="store_true", 74 dest="dontaudit", default=False, 75 help="generate policy with dontaudit rules") 76 parser.add_option("-R", "--reference", action="store_true", dest="refpolicy", 77 default=True, help="generate refpolicy style output") 78 79 parser.add_option("-N", "--noreference", action="store_false", dest="refpolicy", 80 default=False, help="do not generate refpolicy style output") 81 parser.add_option("-v", "--verbose", action="store_true", dest="verbose", 82 default=False, help="explain generated output") 83 parser.add_option("-e", "--explain", action="store_true", dest="explain_long", 84 default=False, help="fully explain generated output") 85 parser.add_option("-t", "--type", help="only process messages with a type that matches this regex", 86 dest="type") 87 parser.add_option("--perm-map", dest="perm_map", help="file name of perm map") 88 parser.add_option("--interface-info", dest="interface_info", help="file name of interface information") 89 parser.add_option("-x", "--xperms", action="store_true", dest="xperms", 90 default=False, help="generate extended permission rules") 91 parser.add_option("--debug", dest="debug", action="store_true", default=False, 92 help="leave generated modules for -M") 93 parser.add_option("-w", "--why", dest="audit2why", action="store_true", default=(os.path.basename(sys.argv[0]) == "audit2why"), 94 help="Translates SELinux audit messages into a description of why the access was denied") 95 96 options, args = parser.parse_args() 97 98 # Make -d, -a, and -i conflict 99 if options.audit is True or options.boot: 100 if options.input is not None: 101 sys.stderr.write("error: --all/--boot conflicts with --input\n") 102 if options.dmesg is True: 103 sys.stderr.write("error: --all/--boot conflicts with --dmesg\n") 104 if options.input is not None and options.dmesg is True: 105 sys.stderr.write("error: --input conflicts with --dmesg\n") 106 107 # Turn on requires generation if a module name is given. Also verify 108 # the module name. 109 if options.module: 110 name = options.module 111 else: 112 name = options.module_package 113 if name: 114 options.requires = True 115 if not module.is_valid_name(name): 116 sys.stderr.write('error: module names must begin with a letter, optionally followed by letters, numbers, "-", "_", "."\n') 117 sys.exit(2) 118 119 # Make -M and -o conflict 120 if options.module_package: 121 if options.output: 122 sys.stderr.write("error: --module-package conflicts with --output\n") 123 sys.exit(2) 124 if options.module: 125 sys.stderr.write("error: --module-package conflicts with --module\n") 126 sys.exit(2) 127 128 self.__options = options 129 130 def __read_input(self): 131 parser = audit.AuditParser(last_load_only=self.__options.lastreload) 132 133 filename = None 134 messages = None 135 f = None 136 137 # Figure out what input we want 138 if self.__options.input is not None: 139 filename = self.__options.input 140 elif self.__options.dmesg: 141 messages = audit.get_dmesg_msgs() 142 elif self.__options.audit: 143 try: 144 messages = audit.get_audit_msgs() 145 except OSError as e: 146 sys.stderr.write('could not run ausearch - "%s"\n' % str(e)) 147 sys.exit(1) 148 elif self.__options.boot: 149 try: 150 messages = audit.get_audit_boot_msgs() 151 except OSError as e: 152 sys.stderr.write('could not run ausearch - "%s"\n' % str(e)) 153 sys.exit(1) 154 else: 155 # This is the default if no input is specified 156 f = sys.stdin 157 158 # Get the input 159 if filename is not None: 160 try: 161 f = open(filename) 162 except IOError as e: 163 sys.stderr.write('could not open file %s - "%s"\n' % (filename, str(e))) 164 sys.exit(1) 165 166 if f is not None: 167 parser.parse_file(f) 168 f.close() 169 170 if messages is not None: 171 parser.parse_string(messages) 172 173 self.__parser = parser 174 175 def __process_input(self): 176 if self.__options.type: 177 avcfilter = audit.AVCTypeFilter(self.__options.type) 178 self.__avs = self.__parser.to_access(avcfilter) 179 csfilter = audit.ComputeSidTypeFilter(self.__options.type) 180 self.__role_types = self.__parser.to_role(csfilter) 181 else: 182 self.__avs = self.__parser.to_access() 183 self.__role_types = self.__parser.to_role() 184 185 def __load_interface_info(self): 186 # Load interface info file 187 if self.__options.interface_info: 188 fn = self.__options.interface_info 189 else: 190 fn = defaults.interface_info() 191 try: 192 fd = open(fn) 193 except: 194 sys.stderr.write("could not open interface info [%s]\n" % fn) 195 sys.exit(1) 196 197 ifs = interfaces.InterfaceSet() 198 ifs.from_file(fd) 199 fd.close() 200 201 # Also load perm maps 202 if self.__options.perm_map: 203 fn = self.__options.perm_map 204 else: 205 fn = defaults.perm_map() 206 try: 207 fd = open(fn) 208 except: 209 sys.stderr.write("could not open perm map [%s]\n" % fn) 210 sys.exit(1) 211 212 perm_maps = objectmodel.PermMappings() 213 perm_maps.from_file(fd) 214 215 return (ifs, perm_maps) 216 217 def __output_modulepackage(self, writer, generator): 218 generator.set_module_name(self.__options.module_package) 219 filename = self.__options.module_package + ".te" 220 packagename = self.__options.module_package + ".pp" 221 222 try: 223 fd = open(filename, "w") 224 except IOError as e: 225 sys.stderr.write("could not write output file: %s\n" % str(e)) 226 sys.exit(1) 227 228 writer.write(generator.get_module(), fd) 229 fd.close() 230 231 mc = module.ModuleCompiler() 232 233 try: 234 mc.create_module_package(filename, self.__options.refpolicy) 235 except RuntimeError as e: 236 print(e) 237 sys.exit(1) 238 239 sys.stdout.write(_("******************** IMPORTANT ***********************\n")) 240 sys.stdout.write((_("To make this policy package active, execute:" + 241 "\n\nsemodule -i %s\n\n") % packagename)) 242 243 def __output_audit2why(self): 244 import selinux 245 try: 246 import sepolicy 247 except (ImportError, ValueError): 248 sepolicy = None 249 for i in self.__parser.avc_msgs: 250 rc = i.type 251 data = i.data 252 if rc >= 0: 253 print("%s\n\tWas caused by:" % i.message) 254 if rc == audit2why.ALLOW: 255 print("\t\tUnknown - would be allowed by active policy") 256 print("\t\tPossible mismatch between this policy and the one under which the audit message was generated.\n") 257 print("\t\tPossible mismatch between current in-memory boolean settings vs. permanent ones.\n") 258 continue 259 if rc == audit2why.DONTAUDIT: 260 print("\t\tUnknown - should be dontaudit'd by active policy") 261 print("\t\tPossible mismatch between this policy and the one under which the audit message was generated.\n") 262 print("\t\tPossible mismatch between current in-memory boolean settings vs. permanent ones.\n") 263 continue 264 if rc == audit2why.BOOLEAN: 265 if len(data) > 1: 266 print("\tOne of the following booleans was set incorrectly.") 267 for b in data: 268 if sepolicy is not None: 269 print("\tDescription:\n\t%s\n" % sepolicy.boolean_desc(b[0])) 270 print("\tAllow access by executing:\n\t# setsebool -P %s %d" % (b[0], b[1])) 271 else: 272 print("\tThe boolean %s was set incorrectly. " % (data[0][0])) 273 if sepolicy is not None: 274 print("\tDescription:\n\t%s\n" % sepolicy.boolean_desc(data[0][0])) 275 print("\tAllow access by executing:\n\t# setsebool -P %s %d" % (data[0][0], data[0][1])) 276 continue 277 278 if rc == audit2why.TERULE: 279 print("\t\tMissing type enforcement (TE) allow rule.\n") 280 print("\t\tYou can use audit2allow to generate a loadable module to allow this access.\n") 281 continue 282 283 if rc == audit2why.CONSTRAINT: 284 print() # !!!! This avc is a constraint violation. You would need to modify the attributes of either the source or target types to allow this access.\n" 285 print("#Constraint rule:") 286 print("\n#\t" + data[0]) 287 for reason in data[1:]: 288 print("#\tPossible cause is the source %s and target %s are different.\n" % reason) 289 290 if rc == audit2why.RBAC: 291 print("\t\tMissing role allow rule.\n") 292 print("\t\tAdd an allow rule for the role pair.\n") 293 continue 294 295 if rc == audit2why.BOUNDS: 296 print("\t\tTypebounds violation.\n") 297 print("\t\tAdd an allow rule for the parent type.\n") 298 continue 299 300 audit2why.finish() 301 return 302 303 def __output(self): 304 305 if self.__options.audit2why: 306 try: 307 return self.__output_audit2why() 308 except RuntimeError as e: 309 print(e) 310 sys.exit(1) 311 312 g = policygen.PolicyGenerator() 313 314 g.set_gen_dontaudit(self.__options.dontaudit) 315 316 if self.__options.module: 317 g.set_module_name(self.__options.module) 318 319 # Interface generation 320 if self.__options.refpolicy: 321 ifs, perm_maps = self.__load_interface_info() 322 g.set_gen_refpol(ifs, perm_maps) 323 324 # Extended permissions 325 if self.__options.xperms: 326 g.set_gen_xperms(True) 327 328 # Explanation 329 if self.__options.verbose: 330 g.set_gen_explain(policygen.SHORT_EXPLANATION) 331 if self.__options.explain_long: 332 g.set_gen_explain(policygen.LONG_EXPLANATION) 333 334 # Requires 335 if self.__options.requires: 336 g.set_gen_requires(True) 337 338 # Generate the policy 339 g.add_access(self.__avs) 340 g.add_role_types(self.__role_types) 341 342 # Output 343 writer = output.ModuleWriter() 344 345 # Module package 346 if self.__options.module_package: 347 self.__output_modulepackage(writer, g) 348 else: 349 # File or stdout 350 if self.__options.module: 351 g.set_module_name(self.__options.module) 352 353 if self.__options.output: 354 fd = open(self.__options.output, "a") 355 else: 356 fd = sys.stdout 357 writer.write(g.get_module(), fd) 358 359 def main(self): 360 try: 361 self.__parse_options() 362 if self.__options.policy: 363 audit2why.init(self.__options.policy) 364 else: 365 audit2why.init() 366 367 self.__read_input() 368 self.__process_input() 369 self.__output() 370 except KeyboardInterrupt: 371 sys.exit(0) 372 except ValueError as e: 373 print(e) 374 sys.exit(1) 375 except IOError as e: 376 print(e) 377 sys.exit(1) 378 379if __name__ == "__main__": 380 app = AuditToPolicy() 381 app.main() 382