1 #! /usr/bin/python -Es 2 # Authors: Karl MacMillan <kmacmillan (at] mentalrootkit.com> 3 # Authors: Dan Walsh <dwalsh (at] 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 22 import sys 23 import os 24 25 import sepolgen.audit as audit 26 import sepolgen.policygen as policygen 27 import sepolgen.interfaces as interfaces 28 import sepolgen.output as output 29 import sepolgen.objectmodel as objectmodel 30 import sepolgen.defaults as defaults 31 import sepolgen.module as module 32 from sepolgen.sepolgeni18n import _ 33 import selinux.audit2why as audit2why 34 import locale 35 try: 36 locale.setlocale(locale.LC_ALL, '') 37 except: 38 pass 39 40 41 class 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("--debug", dest="debug", action="store_true", default=False, 90 help="leave generated modules for -M") 91 parser.add_option("-w", "--why", dest="audit2why", action="store_true", default=(os.path.basename(sys.argv[0]) == "audit2why"), 92 help="Translates SELinux audit messages into a description of why the access was denied") 93 94 options, args = parser.parse_args() 95 96 # Make -d, -a, and -i conflict 97 if options.audit is True or options.boot: 98 if options.input is not None: 99 sys.stderr.write("error: --all/--boot conflicts with --input\n") 100 if options.dmesg is True: 101 sys.stderr.write("error: --all/--boot conflicts with --dmesg\n") 102 if options.input is not None and options.dmesg is True: 103 sys.stderr.write("error: --input conflicts with --dmesg\n") 104 105 # Turn on requires generation if a module name is given. Also verify 106 # the module name. 107 if options.module: 108 name = options.module 109 else: 110 name = options.module_package 111 if name: 112 options.requires = True 113 if not module.is_valid_name(name): 114 sys.stderr.write('error: module names must begin with a letter, optionally followed by letters, numbers, "-", "_", "."\n') 115 sys.exit(2) 116 117 # Make -M and -o conflict 118 if options.module_package: 119 if options.output: 120 sys.stderr.write("error: --module-package conflicts with --output\n") 121 sys.exit(2) 122 if options.module: 123 sys.stderr.write("error: --module-package conflicts with --module\n") 124 sys.exit(2) 125 126 self.__options = options 127 128 def __read_input(self): 129 parser = audit.AuditParser(last_load_only=self.__options.lastreload) 130 131 filename = None 132 messages = None 133 f = None 134 135 # Figure out what input we want 136 if self.__options.input is not None: 137 filename = self.__options.input 138 elif self.__options.dmesg: 139 messages = audit.get_dmesg_msgs() 140 elif self.__options.audit: 141 try: 142 messages = audit.get_audit_msgs() 143 except OSError as e: 144 sys.stderr.write('could not run ausearch - "%s"\n' % str(e)) 145 sys.exit(1) 146 elif self.__options.boot: 147 try: 148 messages = audit.get_audit_boot_msgs() 149 except OSError as e: 150 sys.stderr.write('could not run ausearch - "%s"\n' % str(e)) 151 sys.exit(1) 152 else: 153 # This is the default if no input is specified 154 f = sys.stdin 155 156 # Get the input 157 if filename is not None: 158 try: 159 f = open(filename) 160 except IOError as e: 161 sys.stderr.write('could not open file %s - "%s"\n' % (filename, str(e))) 162 sys.exit(1) 163 164 if f is not None: 165 parser.parse_file(f) 166 f.close() 167 168 if messages is not None: 169 parser.parse_string(messages) 170 171 self.__parser = parser 172 173 def __process_input(self): 174 if self.__options.type: 175 avcfilter = audit.AVCTypeFilter(self.__options.type) 176 self.__avs = self.__parser.to_access(avcfilter) 177 csfilter = audit.ComputeSidTypeFilter(self.__options.type) 178 self.__role_types = self.__parser.to_role(csfilter) 179 else: 180 self.__avs = self.__parser.to_access() 181 self.__role_types = self.__parser.to_role() 182 183 def __load_interface_info(self): 184 # Load interface info file 185 if self.__options.interface_info: 186 fn = self.__options.interface_info 187 else: 188 fn = defaults.interface_info() 189 try: 190 fd = open(fn) 191 except: 192 sys.stderr.write("could not open interface info [%s]\n" % fn) 193 sys.exit(1) 194 195 ifs = interfaces.InterfaceSet() 196 ifs.from_file(fd) 197 fd.close() 198 199 # Also load perm maps 200 if self.__options.perm_map: 201 fn = self.__options.perm_map 202 else: 203 fn = defaults.perm_map() 204 try: 205 fd = open(fn) 206 except: 207 sys.stderr.write("could not open perm map [%s]\n" % fn) 208 sys.exit(1) 209 210 perm_maps = objectmodel.PermMappings() 211 perm_maps.from_file(fd) 212 213 return (ifs, perm_maps) 214 215 def __output_modulepackage(self, writer, generator): 216 generator.set_module_name(self.__options.module_package) 217 filename = self.__options.module_package + ".te" 218 packagename = self.__options.module_package + ".pp" 219 220 try: 221 fd = open(filename, "w") 222 except IOError as e: 223 sys.stderr.write("could not write output file: %s\n" % str(e)) 224 sys.exit(1) 225 226 writer.write(generator.get_module(), fd) 227 fd.close() 228 229 mc = module.ModuleCompiler() 230 231 try: 232 mc.create_module_package(filename, self.__options.refpolicy) 233 except RuntimeError as e: 234 print(e) 235 sys.exit(1) 236 237 sys.stdout.write(_("******************** IMPORTANT ***********************\n")) 238 sys.stdout.write((_("To make this policy package active, execute:" + 239 "\n\nsemodule -i %s\n\n") % packagename)) 240 241 def __output_audit2why(self): 242 import selinux 243 import sepolicy 244 for i in self.__parser.avc_msgs: 245 rc = i.type 246 data = i.data 247 if rc >= 0: 248 print("%s\n\tWas caused by:" % i.message) 249 if rc == audit2why.ALLOW: 250 print("\t\tUnknown - would be allowed by active policy") 251 print("\t\tPossible mismatch between this policy and the one under which the audit message was generated.\n") 252 print("\t\tPossible mismatch between current in-memory boolean settings vs. permanent ones.\n") 253 continue 254 if rc == audit2why.DONTAUDIT: 255 print("\t\tUnknown - should be dontaudit'd 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.BOOLEAN: 260 if len(data) > 1: 261 print("\tOne of the following booleans was set incorrectly.") 262 for b in data: 263 print("\tDescription:\n\t%s\n" % sepolicy.boolean_desc(b[0])) 264 print("\tAllow access by executing:\n\t# setsebool -P %s %d" % (b[0], b[1])) 265 else: 266 print("\tThe boolean %s was set incorrectly. " % (data[0][0])) 267 print("\tDescription:\n\t%s\n" % sepolicy.boolean_desc(data[0][0])) 268 print("\tAllow access by executing:\n\t# setsebool -P %s %d" % (data[0][0], data[0][1])) 269 continue 270 271 if rc == audit2why.TERULE: 272 print("\t\tMissing type enforcement (TE) allow rule.\n") 273 print("\t\tYou can use audit2allow to generate a loadable module to allow this access.\n") 274 continue 275 276 if rc == audit2why.CONSTRAINT: 277 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" 278 print("#Constraint rule:") 279 print("\n#\t" + data[0]) 280 for reason in data[1:]: 281 print("#\tPossible cause is the source %s and target %s are different.\n" % reason) 282 283 if rc == audit2why.RBAC: 284 print("\t\tMissing role allow rule.\n") 285 print("\t\tAdd an allow rule for the role pair.\n") 286 continue 287 288 if rc == audit2why.BOUNDS: 289 print("\t\tTypebounds violation.\n") 290 print("\t\tAdd an allow rule for the parent type.\n") 291 continue 292 293 audit2why.finish() 294 return 295 296 def __output(self): 297 298 if self.__options.audit2why: 299 try: 300 return self.__output_audit2why() 301 except RuntimeError as e: 302 print(e) 303 sys.exit(1) 304 305 g = policygen.PolicyGenerator() 306 307 g.set_gen_dontaudit(self.__options.dontaudit) 308 309 if self.__options.module: 310 g.set_module_name(self.__options.module) 311 312 # Interface generation 313 if self.__options.refpolicy: 314 ifs, perm_maps = self.__load_interface_info() 315 g.set_gen_refpol(ifs, perm_maps) 316 317 # Explanation 318 if self.__options.verbose: 319 g.set_gen_explain(policygen.SHORT_EXPLANATION) 320 if self.__options.explain_long: 321 g.set_gen_explain(policygen.LONG_EXPLANATION) 322 323 # Requires 324 if self.__options.requires: 325 g.set_gen_requires(True) 326 327 # Generate the policy 328 g.add_access(self.__avs) 329 g.add_role_types(self.__role_types) 330 331 # Output 332 writer = output.ModuleWriter() 333 334 # Module package 335 if self.__options.module_package: 336 self.__output_modulepackage(writer, g) 337 else: 338 # File or stdout 339 if self.__options.module: 340 g.set_module_name(self.__options.module) 341 342 if self.__options.output: 343 fd = open(self.__options.output, "a") 344 else: 345 fd = sys.stdout 346 writer.write(g.get_module(), fd) 347 348 def main(self): 349 try: 350 self.__parse_options() 351 if self.__options.policy: 352 audit2why.init(self.__options.policy) 353 else: 354 audit2why.init() 355 356 self.__read_input() 357 self.__process_input() 358 self.__output() 359 except KeyboardInterrupt: 360 sys.exit(0) 361 except ValueError as e: 362 print(e) 363 sys.exit(1) 364 except IOError as e: 365 print(e) 366 sys.exit(1) 367 368 if __name__ == "__main__": 369 app = AuditToPolicy() 370 app.main() 371