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 refpolicy
28import objectmodel
29import access
30import interfaces
31import matching
32import selinux.audit2why as audit2why
33try:
34    from setools import *
35except:
36    pass
37
38# Constants for the level of explanation from the generation
39# routines
40NO_EXPLANATION    = 0
41SHORT_EXPLANATION = 1
42LONG_EXPLANATION  = 2
43
44class PolicyGenerator:
45    """Generate a reference policy module from access vectors.
46
47    PolicyGenerator generates a new reference policy module
48    or updates an existing module based on requested access
49    in the form of access vectors.
50
51    It generates allow rules and optionally module require
52    statements and reference policy interfaces. By default
53    only allow rules are generated. The methods .set_gen_refpol
54    and .set_gen_requires turns on interface generation and
55    requires generation respectively.
56
57    PolicyGenerator can also optionally add comments explaining
58    why a particular access was allowed based on the audit
59    messages that generated the access. The access vectors
60    passed in must have the .audit_msgs field set correctly
61    and .explain set to SHORT|LONG_EXPLANATION to enable this
62    feature.
63
64    The module created by PolicyGenerator can be passed to
65    output.ModuleWriter to output a text representation.
66    """
67    def __init__(self, module=None):
68        """Initialize a PolicyGenerator with an optional
69        existing module.
70
71        If the module paramater is not None then access
72        will be added to the passed in module. Otherwise
73        a new reference policy module will be created.
74        """
75        self.ifgen = None
76        self.explain = NO_EXPLANATION
77        self.gen_requires = False
78        if module:
79            self.moduel = module
80        else:
81            self.module = refpolicy.Module()
82
83        self.dontaudit = False
84
85        self.domains = None
86    def set_gen_refpol(self, if_set=None, perm_maps=None):
87        """Set whether reference policy interfaces are generated.
88
89        To turn on interface generation pass in an interface set
90        to use for interface generation. To turn off interface
91        generation pass in None.
92
93        If interface generation is enabled requires generation
94        will also be enabled.
95        """
96        if if_set:
97            self.ifgen = InterfaceGenerator(if_set, perm_maps)
98            self.gen_requires = True
99        else:
100            self.ifgen = None
101        self.__set_module_style()
102
103
104    def set_gen_requires(self, status=True):
105        """Set whether module requires are generated.
106
107        Passing in true will turn on requires generation and
108        False will disable generation. If requires generation is
109        disabled interface generation will also be disabled and
110        can only be re-enabled via .set_gen_refpol.
111        """
112        self.gen_requires = status
113
114    def set_gen_explain(self, explain=SHORT_EXPLANATION):
115        """Set whether access is explained.
116        """
117        self.explain = explain
118
119    def set_gen_dontaudit(self, dontaudit):
120        self.dontaudit = dontaudit
121
122    def __set_module_style(self):
123        if self.ifgen:
124            refpolicy = True
125        else:
126            refpolicy = False
127        for mod in self.module.module_declarations():
128            mod.refpolicy = refpolicy
129
130    def set_module_name(self, name, version="1.0"):
131        """Set the name of the module and optionally the version.
132        """
133        # find an existing module declaration
134        m = None
135        for mod in self.module.module_declarations():
136            m = mod
137        if not m:
138            m = refpolicy.ModuleDeclaration()
139            self.module.children.insert(0, m)
140        m.name = name
141        m.version = version
142        if self.ifgen:
143            m.refpolicy = True
144        else:
145            m.refpolicy = False
146
147    def get_module(self):
148        # Generate the requires
149        if self.gen_requires:
150            gen_requires(self.module)
151
152        """Return the generated module"""
153        return self.module
154
155    def __add_allow_rules(self, avs):
156        for av in avs:
157            rule = refpolicy.AVRule(av)
158            if self.dontaudit:
159                rule.rule_type = rule.DONTAUDIT
160            rule.comment = ""
161            if self.explain:
162                rule.comment = str(refpolicy.Comment(explain_access(av, verbosity=self.explain)))
163            if av.type == audit2why.ALLOW:
164                rule.comment += "\n#!!!! This avc is allowed in the current policy"
165            if av.type == audit2why.DONTAUDIT:
166                rule.comment += "\n#!!!! This avc has a dontaudit rule in the current policy"
167
168            if av.type == audit2why.BOOLEAN:
169                if len(av.data) > 1:
170                    rule.comment += "\n#!!!! This avc can be allowed using one of the these booleans:\n#     %s" % ", ".join(map(lambda x: x[0], av.data))
171                else:
172                    rule.comment += "\n#!!!! This avc can be allowed using the boolean '%s'" % av.data[0][0]
173
174            if av.type == audit2why.CONSTRAINT:
175                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."
176                rule.comment += "\n#Constraint rule: "
177                rule.comment += "\n\t" + av.data[0]
178                for reason in av.data[1:]:
179                    rule.comment += "\n#\tPossible cause is the source %s and target %s are different." % reason
180
181            try:
182                if ( av.type == audit2why.TERULE and
183                     "write" in av.perms and
184                     ( "dir" in av.obj_class or "open" in av.perms )):
185                    if not self.domains:
186                        self.domains = seinfo(ATTRIBUTE, name="domain")[0]["types"]
187                    types=[]
188
189                    for i in map(lambda x: x[TCONTEXT], sesearch([ALLOW], {SCONTEXT: av.src_type, CLASS: av.obj_class, PERMS: av.perms})):
190                        if i not in self.domains:
191                            types.append(i)
192                    if len(types) == 1:
193                        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))
194                    elif len(types) >= 1:
195                        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))
196            except:
197                pass
198            self.module.children.append(rule)
199
200
201    def add_access(self, av_set):
202        """Add the access from the access vector set to this
203        module.
204        """
205        # Use the interface generator to split the access
206        # into raw allow rules and interfaces. After this
207        # a will contain a list of access that should be
208        # used as raw allow rules and the interfaces will
209        # be added to the module.
210        if self.ifgen:
211            raw_allow, ifcalls = self.ifgen.gen(av_set, self.explain)
212            self.module.children.extend(ifcalls)
213        else:
214            raw_allow = av_set
215
216        # Generate the raw allow rules from the filtered list
217        self.__add_allow_rules(raw_allow)
218
219    def add_role_types(self, role_type_set):
220        for role_type in role_type_set:
221            self.module.children.append(role_type)
222
223def explain_access(av, ml=None, verbosity=SHORT_EXPLANATION):
224    """Explain why a policy statement was generated.
225
226    Return a string containing a text explanation of
227    why a policy statement was generated. The string is
228    commented and wrapped and can be directly inserted
229    into a policy.
230
231    Params:
232      av - access vector representing the access. Should
233       have .audit_msgs set appropriately.
234      verbosity - the amount of explanation provided. Should
235       be set to NO_EXPLANATION, SHORT_EXPLANATION, or
236       LONG_EXPLANATION.
237    Returns:
238      list of strings - strings explaining the access or an empty
239       string if verbosity=NO_EXPLANATION or there is not sufficient
240       information to provide an explanation.
241    """
242    s = []
243
244    def explain_interfaces():
245        if not ml:
246            return
247        s.append(" Interface options:")
248        for match in ml.all():
249            ifcall = call_interface(match.interface, ml.av)
250            s.append('   %s # [%d]' % (ifcall.to_string(), match.dist))
251
252
253    # Format the raw audit data to explain why the
254    # access was requested - either long or short.
255    if verbosity == LONG_EXPLANATION:
256        for msg in av.audit_msgs:
257            s.append(' %s' % msg.header)
258            s.append('  scontext="%s" tcontext="%s"' %
259                     (str(msg.scontext), str(msg.tcontext)))
260            s.append('  class="%s" perms="%s"' %
261                     (msg.tclass, refpolicy.list_to_space_str(msg.accesses)))
262            s.append('  comm="%s" exe="%s" path="%s"' % (msg.comm, msg.exe, msg.path))
263            s.extend(textwrap.wrap('message="' + msg.message + '"', 80, initial_indent="  ",
264                                   subsequent_indent="   "))
265        explain_interfaces()
266    elif verbosity:
267        s.append(' src="%s" tgt="%s" class="%s", perms="%s"' %
268                 (av.src_type, av.tgt_type, av.obj_class, av.perms.to_space_str()))
269        # For the short display we are only going to use the additional information
270        # from the first audit message. For the vast majority of cases this info
271        # will always be the same anyway.
272        if len(av.audit_msgs) > 0:
273            msg = av.audit_msgs[0]
274            s.append(' comm="%s" exe="%s" path="%s"' % (msg.comm, msg.exe, msg.path))
275        explain_interfaces()
276    return s
277
278def param_comp(a, b):
279    return cmp(b.num, a.num)
280
281def call_interface(interface, av):
282    params = []
283    args = []
284
285    params.extend(interface.params.values())
286    params.sort(param_comp)
287
288    ifcall = refpolicy.InterfaceCall()
289    ifcall.ifname = interface.name
290
291    for i in range(len(params)):
292        if params[i].type == refpolicy.SRC_TYPE:
293            ifcall.args.append(av.src_type)
294        elif params[i].type == refpolicy.TGT_TYPE:
295            ifcall.args.append(av.tgt_type)
296        elif params[i].type == refpolicy.OBJ_CLASS:
297            ifcall.args.append(av.obj_class)
298        else:
299            print params[i].type
300            assert(0)
301
302    assert(len(ifcall.args) > 0)
303
304    return ifcall
305
306class InterfaceGenerator:
307    def __init__(self, ifs, perm_maps=None):
308        self.ifs = ifs
309        self.hack_check_ifs(ifs)
310        self.matcher = matching.AccessMatcher(perm_maps)
311        self.calls = []
312
313    def hack_check_ifs(self, ifs):
314        # FIXME: Disable interfaces we can't call - this is a hack.
315        # Because we don't handle roles, multiple paramaters, etc.,
316        # etc., we must make certain we can actually use a returned
317        # interface.
318        for x in ifs.interfaces.values():
319            params = []
320            params.extend(x.params.values())
321            params.sort(param_comp)
322            for i in range(len(params)):
323                # Check that the paramater position matches
324                # the number (e.g., $1 is the first arg). This
325                # will fail if the parser missed something.
326                if (i + 1) != params[i].num:
327                    x.enabled = False
328                    break
329                # Check that we can handle the param type (currently excludes
330                # roles.
331                if params[i].type not in [refpolicy.SRC_TYPE, refpolicy.TGT_TYPE,
332                                          refpolicy.OBJ_CLASS]:
333                    x.enabled = False
334                    break
335
336    def gen(self, avs, verbosity):
337        raw_av = self.match(avs)
338        ifcalls = []
339        for ml in self.calls:
340            ifcall = call_interface(ml.best().interface, ml.av)
341            if verbosity:
342                ifcall.comment = refpolicy.Comment(explain_access(ml.av, ml, verbosity))
343            ifcalls.append((ifcall, ml))
344
345        d = []
346        for ifcall, ifs in ifcalls:
347            found = False
348            for o_ifcall in d:
349                if o_ifcall.matches(ifcall):
350                    if o_ifcall.comment and ifcall.comment:
351                        o_ifcall.comment.merge(ifcall.comment)
352                    found = True
353            if not found:
354                d.append(ifcall)
355
356        return (raw_av, d)
357
358
359    def match(self, avs):
360        raw_av = []
361        for av in avs:
362            ans = matching.MatchList()
363            self.matcher.search_ifs(self.ifs, av, ans)
364            if len(ans):
365                self.calls.append(ans)
366            else:
367                raw_av.append(av)
368
369        return raw_av
370
371
372def gen_requires(module):
373    """Add require statements to the module.
374    """
375    def collect_requires(node):
376        r = refpolicy.Require()
377        for avrule in node.avrules():
378            r.types.update(avrule.src_types)
379            r.types.update(avrule.tgt_types)
380            for obj in avrule.obj_classes:
381                r.add_obj_class(obj, avrule.perms)
382
383        for ifcall in node.interface_calls():
384            for arg in ifcall.args:
385                # FIXME - handle non-type arguments when we
386                # can actually figure those out.
387                r.types.add(arg)
388
389        for role_type in node.role_types():
390            r.roles.add(role_type.role)
391            r.types.update(role_type.types)
392
393        r.types.discard("self")
394
395        node.children.insert(0, r)
396
397    # FUTURE - this is untested on modules with any sort of
398    # nesting
399    for node in module.nodes():
400        collect_requires(node)
401
402
403