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