blob: 34c8401078551d1bd361db58d7e6cc5cf4020814 [file] [log] [blame]
Jeff Vander Stoep74e4f932016-02-08 15:27:10 -08001# 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