• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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