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