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