382 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
			
		
		
	
	
			382 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
| #!/usr/bin/python3 -Es
 | |
| # Authors: Karl MacMillan <kmacmillan@mentalrootkit.com>
 | |
| # Authors: Dan Walsh <dwalsh@redhat.com>
 | |
| #
 | |
| # Copyright (C) 2006-2013  Red Hat
 | |
| # see file 'COPYING' for use and warranty information
 | |
| #
 | |
| # This program is free software; you can redistribute it and/or
 | |
| # modify it under the terms of the GNU General Public License as
 | |
| # published by the Free Software Foundation; version 2 only
 | |
| #
 | |
| # This program is distributed in the hope that it will be useful,
 | |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
| # GNU General Public License for more details.
 | |
| #
 | |
| # You should have received a copy of the GNU General Public License
 | |
| # along with this program; if not, write to the Free Software
 | |
| # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 | |
| #
 | |
| 
 | |
| import sys
 | |
| import os
 | |
| 
 | |
| import sepolgen.audit as audit
 | |
| import sepolgen.policygen as policygen
 | |
| import sepolgen.interfaces as interfaces
 | |
| import sepolgen.output as output
 | |
| import sepolgen.objectmodel as objectmodel
 | |
| import sepolgen.defaults as defaults
 | |
| import sepolgen.module as module
 | |
| from sepolgen.sepolgeni18n import _
 | |
| import selinux.audit2why as audit2why
 | |
| import locale
 | |
| try:
 | |
|     locale.setlocale(locale.LC_ALL, '')
 | |
| except:
 | |
|     pass
 | |
| 
 | |
| 
 | |
| class AuditToPolicy:
 | |
|     VERSION = "%prog .1"
 | |
|     SYSLOG = "/var/log/messages"
 | |
| 
 | |
|     def __init__(self):
 | |
|         self.__options = None
 | |
|         self.__parser = None
 | |
|         self.__avs = None
 | |
| 
 | |
|     def __parse_options(self):
 | |
|         from optparse import OptionParser
 | |
| 
 | |
|         parser = OptionParser(version=self.VERSION)
 | |
|         parser.add_option("-b", "--boot", action="store_true", dest="boot", default=False,
 | |
|                           help="audit messages since last boot conflicts with -i")
 | |
|         parser.add_option("-a", "--all", action="store_true", dest="audit", default=False,
 | |
|                           help="read input from audit log - conflicts with -i")
 | |
|         parser.add_option("-p", "--policy", dest="policy", default=None, help="Policy file to use for analysis")
 | |
|         parser.add_option("-d", "--dmesg", action="store_true", dest="dmesg", default=False,
 | |
|                           help="read input from dmesg - conflicts with --all and --input")
 | |
|         parser.add_option("-i", "--input", dest="input",
 | |
|                           help="read input from <input> - conflicts with -a")
 | |
|         parser.add_option("-l", "--lastreload", action="store_true", dest="lastreload", default=False,
 | |
|                           help="read input only after the last reload")
 | |
|         parser.add_option("-r", "--requires", action="store_true", dest="requires", default=False,
 | |
|                           help="generate require statements for rules")
 | |
|         parser.add_option("-m", "--module", dest="module",
 | |
|                           help="set the module name - implies --requires")
 | |
|         parser.add_option("-M", "--module-package", dest="module_package",
 | |
|                           help="generate a module package - conflicts with -o and -m")
 | |
|         parser.add_option("-o", "--output", dest="output",
 | |
|                           help="append output to <filename>, conflicts with -M")
 | |
|         parser.add_option("-D", "--dontaudit", action="store_true",
 | |
|                           dest="dontaudit", default=False,
 | |
|                           help="generate policy with dontaudit rules")
 | |
|         parser.add_option("-R", "--reference", action="store_true", dest="refpolicy",
 | |
|                           default=True, help="generate refpolicy style output")
 | |
| 
 | |
|         parser.add_option("-N", "--noreference", action="store_false", dest="refpolicy",
 | |
|                           default=False, help="do not generate refpolicy style output")
 | |
|         parser.add_option("-v", "--verbose", action="store_true", dest="verbose",
 | |
|                           default=False, help="explain generated output")
 | |
|         parser.add_option("-e", "--explain", action="store_true", dest="explain_long",
 | |
|                           default=False, help="fully explain generated output")
 | |
|         parser.add_option("-t", "--type", help="only process messages with a type that matches this regex",
 | |
|                           dest="type")
 | |
|         parser.add_option("--perm-map", dest="perm_map", help="file name of perm map")
 | |
|         parser.add_option("--interface-info", dest="interface_info", help="file name of interface information")
 | |
|         parser.add_option("-x", "--xperms", action="store_true", dest="xperms",
 | |
|                           default=False, help="generate extended permission rules")
 | |
|         parser.add_option("--debug", dest="debug", action="store_true", default=False,
 | |
|                           help="leave generated modules for -M")
 | |
|         parser.add_option("-w", "--why", dest="audit2why", action="store_true", default=(os.path.basename(sys.argv[0]) == "audit2why"),
 | |
|                           help="Translates SELinux audit messages into a description of why the access was denied")
 | |
| 
 | |
|         options, args = parser.parse_args()
 | |
| 
 | |
|         # Make -d, -a, and -i conflict
 | |
|         if options.audit is True or options.boot:
 | |
|             if options.input is not None:
 | |
|                 sys.stderr.write("error: --all/--boot conflicts with --input\n")
 | |
|             if options.dmesg is True:
 | |
|                 sys.stderr.write("error: --all/--boot conflicts with --dmesg\n")
 | |
|         if options.input is not None and options.dmesg is True:
 | |
|             sys.stderr.write("error: --input conflicts with --dmesg\n")
 | |
| 
 | |
|         # Turn on requires generation if a module name is given. Also verify
 | |
|         # the module name.
 | |
|         if options.module:
 | |
|             name = options.module
 | |
|         else:
 | |
|             name = options.module_package
 | |
|         if name:
 | |
|             options.requires = True
 | |
|             if not module.is_valid_name(name):
 | |
|                 sys.stderr.write('error: module names must begin with a letter, optionally followed by letters, numbers, "-", "_", "."\n')
 | |
|                 sys.exit(2)
 | |
| 
 | |
|         # Make -M and -o conflict
 | |
|         if options.module_package:
 | |
|             if options.output:
 | |
|                 sys.stderr.write("error: --module-package conflicts with --output\n")
 | |
|                 sys.exit(2)
 | |
|             if options.module:
 | |
|                 sys.stderr.write("error: --module-package conflicts with --module\n")
 | |
|                 sys.exit(2)
 | |
| 
 | |
|         self.__options = options
 | |
| 
 | |
|     def __read_input(self):
 | |
|         parser = audit.AuditParser(last_load_only=self.__options.lastreload)
 | |
| 
 | |
|         filename = None
 | |
|         messages = None
 | |
|         f = None
 | |
| 
 | |
|         # Figure out what input we want
 | |
|         if self.__options.input is not None:
 | |
|             filename = self.__options.input
 | |
|         elif self.__options.dmesg:
 | |
|             messages = audit.get_dmesg_msgs()
 | |
|         elif self.__options.audit:
 | |
|             try:
 | |
|                 messages = audit.get_audit_msgs()
 | |
|             except OSError as e:
 | |
|                 sys.stderr.write('could not run ausearch - "%s"\n' % str(e))
 | |
|                 sys.exit(1)
 | |
|         elif self.__options.boot:
 | |
|             try:
 | |
|                 messages = audit.get_audit_boot_msgs()
 | |
|             except OSError as e:
 | |
|                 sys.stderr.write('could not run ausearch - "%s"\n' % str(e))
 | |
|                 sys.exit(1)
 | |
|         else:
 | |
|             # This is the default if no input is specified
 | |
|             f = sys.stdin
 | |
| 
 | |
|         # Get the input
 | |
|         if filename is not None:
 | |
|             try:
 | |
|                 f = open(filename)
 | |
|             except IOError as e:
 | |
|                 sys.stderr.write('could not open file %s - "%s"\n' % (filename, str(e)))
 | |
|                 sys.exit(1)
 | |
| 
 | |
|         if f is not None:
 | |
|             parser.parse_file(f)
 | |
|             f.close()
 | |
| 
 | |
|         if messages is not None:
 | |
|             parser.parse_string(messages)
 | |
| 
 | |
|         self.__parser = parser
 | |
| 
 | |
|     def __process_input(self):
 | |
|         if self.__options.type:
 | |
|             avcfilter = audit.AVCTypeFilter(self.__options.type)
 | |
|             self.__avs = self.__parser.to_access(avcfilter)
 | |
|             csfilter = audit.ComputeSidTypeFilter(self.__options.type)
 | |
|             self.__role_types = self.__parser.to_role(csfilter)
 | |
|         else:
 | |
|             self.__avs = self.__parser.to_access()
 | |
|             self.__role_types = self.__parser.to_role()
 | |
| 
 | |
|     def __load_interface_info(self):
 | |
|         # Load interface info file
 | |
|         if self.__options.interface_info:
 | |
|             fn = self.__options.interface_info
 | |
|         else:
 | |
|             fn = defaults.interface_info()
 | |
|         try:
 | |
|             fd = open(fn)
 | |
|         except:
 | |
|             sys.stderr.write("could not open interface info [%s]\n" % fn)
 | |
|             sys.exit(1)
 | |
| 
 | |
|         ifs = interfaces.InterfaceSet()
 | |
|         ifs.from_file(fd)
 | |
|         fd.close()
 | |
| 
 | |
|         # Also load perm maps
 | |
|         if self.__options.perm_map:
 | |
|             fn = self.__options.perm_map
 | |
|         else:
 | |
|             fn = defaults.perm_map()
 | |
|         try:
 | |
|             fd = open(fn)
 | |
|         except:
 | |
|             sys.stderr.write("could not open perm map [%s]\n" % fn)
 | |
|             sys.exit(1)
 | |
| 
 | |
|         perm_maps = objectmodel.PermMappings()
 | |
|         perm_maps.from_file(fd)
 | |
| 
 | |
|         return (ifs, perm_maps)
 | |
| 
 | |
|     def __output_modulepackage(self, writer, generator):
 | |
|         generator.set_module_name(self.__options.module_package)
 | |
|         filename = self.__options.module_package + ".te"
 | |
|         packagename = self.__options.module_package + ".pp"
 | |
| 
 | |
|         try:
 | |
|             fd = open(filename, "w")
 | |
|         except IOError as e:
 | |
|             sys.stderr.write("could not write output file: %s\n" % str(e))
 | |
|             sys.exit(1)
 | |
| 
 | |
|         writer.write(generator.get_module(), fd)
 | |
|         fd.close()
 | |
| 
 | |
|         mc = module.ModuleCompiler()
 | |
| 
 | |
|         try:
 | |
|             mc.create_module_package(filename, self.__options.refpolicy)
 | |
|         except RuntimeError as e:
 | |
|             print(e)
 | |
|             sys.exit(1)
 | |
| 
 | |
|         sys.stdout.write(_("******************** IMPORTANT ***********************\n"))
 | |
|         sys.stdout.write((_("To make this policy package active, execute:" +
 | |
|                             "\n\nsemodule -i %s\n\n") % packagename))
 | |
| 
 | |
|     def __output_audit2why(self):
 | |
|         import selinux
 | |
|         try:
 | |
|             import sepolicy
 | |
|         except (ImportError, ValueError):
 | |
|             sepolicy = None
 | |
|         for i in self.__parser.avc_msgs:
 | |
|             rc = i.type
 | |
|             data = i.data
 | |
|             if rc >= 0:
 | |
|                 print("%s\n\tWas caused by:" % i.message)
 | |
|             if rc == audit2why.ALLOW:
 | |
|                 print("\t\tUnknown - would be allowed by active policy")
 | |
|                 print("\t\tPossible mismatch between this policy and the one under which the audit message was generated.\n")
 | |
|                 print("\t\tPossible mismatch between current in-memory boolean settings vs. permanent ones.\n")
 | |
|                 continue
 | |
|             if rc == audit2why.DONTAUDIT:
 | |
|                 print("\t\tUnknown - should be dontaudit'd by active policy")
 | |
|                 print("\t\tPossible mismatch between this policy and the one under which the audit message was generated.\n")
 | |
|                 print("\t\tPossible mismatch between current in-memory boolean settings vs. permanent ones.\n")
 | |
|                 continue
 | |
|             if rc == audit2why.BOOLEAN:
 | |
|                 if len(data) > 1:
 | |
|                     print("\tOne of the following booleans was set incorrectly.")
 | |
|                     for b in data:
 | |
|                         if sepolicy is not None:
 | |
|                             print("\tDescription:\n\t%s\n" % sepolicy.boolean_desc(b[0]))
 | |
|                         print("\tAllow access by executing:\n\t# setsebool -P %s %d" % (b[0], b[1]))
 | |
|                 else:
 | |
|                     print("\tThe boolean %s was set incorrectly. " % (data[0][0]))
 | |
|                     if sepolicy is not None:
 | |
|                         print("\tDescription:\n\t%s\n" % sepolicy.boolean_desc(data[0][0]))
 | |
|                     print("\tAllow access by executing:\n\t# setsebool -P %s %d" % (data[0][0], data[0][1]))
 | |
|                 continue
 | |
| 
 | |
|             if rc == audit2why.TERULE:
 | |
|                 print("\t\tMissing type enforcement (TE) allow rule.\n")
 | |
|                 print("\t\tYou can use audit2allow to generate a loadable module to allow this access.\n")
 | |
|                 continue
 | |
| 
 | |
|             if rc == audit2why.CONSTRAINT:
 | |
|                 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"
 | |
|                 print("#Constraint rule:")
 | |
|                 print("\n#\t" + data[0])
 | |
|                 for reason in data[1:]:
 | |
|                     print("#\tPossible cause is the source %s and target %s are different.\n" % reason)
 | |
| 
 | |
|             if rc == audit2why.RBAC:
 | |
|                 print("\t\tMissing role allow rule.\n")
 | |
|                 print("\t\tAdd an allow rule for the role pair.\n")
 | |
|                 continue
 | |
| 
 | |
|             if rc == audit2why.BOUNDS:
 | |
|                 print("\t\tTypebounds violation.\n")
 | |
|                 print("\t\tAdd an allow rule for the parent type.\n")
 | |
|                 continue
 | |
| 
 | |
|         audit2why.finish()
 | |
|         return
 | |
| 
 | |
|     def __output(self):
 | |
| 
 | |
|         if self.__options.audit2why:
 | |
|             try:
 | |
|                 return self.__output_audit2why()
 | |
|             except RuntimeError as e:
 | |
|                 print(e)
 | |
|                 sys.exit(1)
 | |
| 
 | |
|         g = policygen.PolicyGenerator()
 | |
| 
 | |
|         g.set_gen_dontaudit(self.__options.dontaudit)
 | |
| 
 | |
|         if self.__options.module:
 | |
|             g.set_module_name(self.__options.module)
 | |
| 
 | |
|         # Interface generation
 | |
|         if self.__options.refpolicy:
 | |
|             ifs, perm_maps = self.__load_interface_info()
 | |
|             g.set_gen_refpol(ifs, perm_maps)
 | |
| 
 | |
|         # Extended permissions
 | |
|         if self.__options.xperms:
 | |
|             g.set_gen_xperms(True)
 | |
| 
 | |
|         # Explanation
 | |
|         if self.__options.verbose:
 | |
|             g.set_gen_explain(policygen.SHORT_EXPLANATION)
 | |
|         if self.__options.explain_long:
 | |
|             g.set_gen_explain(policygen.LONG_EXPLANATION)
 | |
| 
 | |
|         # Requires
 | |
|         if self.__options.requires:
 | |
|             g.set_gen_requires(True)
 | |
| 
 | |
|         # Generate the policy
 | |
|         g.add_access(self.__avs)
 | |
|         g.add_role_types(self.__role_types)
 | |
| 
 | |
|         # Output
 | |
|         writer = output.ModuleWriter()
 | |
| 
 | |
|         # Module package
 | |
|         if self.__options.module_package:
 | |
|             self.__output_modulepackage(writer, g)
 | |
|         else:
 | |
|             # File or stdout
 | |
|             if self.__options.module:
 | |
|                 g.set_module_name(self.__options.module)
 | |
| 
 | |
|             if self.__options.output:
 | |
|                 fd = open(self.__options.output, "a")
 | |
|             else:
 | |
|                 fd = sys.stdout
 | |
|             writer.write(g.get_module(), fd)
 | |
| 
 | |
|     def main(self):
 | |
|         try:
 | |
|             self.__parse_options()
 | |
|             if self.__options.policy:
 | |
|                 audit2why.init(self.__options.policy)
 | |
|             else:
 | |
|                 audit2why.init()
 | |
| 
 | |
|             self.__read_input()
 | |
|             self.__process_input()
 | |
|             self.__output()
 | |
|         except KeyboardInterrupt:
 | |
|             sys.exit(0)
 | |
|         except ValueError as e:
 | |
|             print(e)
 | |
|             sys.exit(1)
 | |
|         except IOError as e:
 | |
|             print(e)
 | |
|             sys.exit(1)
 | |
| 
 | |
| if __name__ == "__main__":
 | |
|     app = AuditToPolicy()
 | |
|     app.main()
 |