blob: 6f86359d4e5550c518fce8172f25a44ae4fdb602 [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 matching requested access to access vectors.
22"""
23
24import itertools
25
26from . import access
27from . import objectmodel
28from . import util
29
30
31class Match(util.Comparison):
32 def __init__(self, interface=None, dist=0):
33 self.interface = interface
34 self.dist = dist
35 self.info_dir_change = False
36 # when implementing __eq__ also __hash__ is needed on py2
37 # if object is muttable __hash__ should be None
38 self.__hash__ = None
39
40 def _compare(self, other, method):
41 try:
42 a = (self.dist, self.info_dir_change)
43 b = (other.dist, other.info_dir_change)
44 return method(a, b)
45 except (AttributeError, TypeError):
46 # trying to compare to foreign type
47 return NotImplemented
48
49class MatchList:
50 DEFAULT_THRESHOLD = 150
51 def __init__(self):
52 # Match objects that pass the threshold
53 self.children = []
54 # Match objects over the threshold
55 self.bastards = []
56 self.threshold = self.DEFAULT_THRESHOLD
57 self.allow_info_dir_change = False
58 self.av = None
59
60 def best(self):
61 if len(self.children):
62 return self.children[0]
63 if len(self.bastards):
64 return self.bastards[0]
65 return None
66
67 def __len__(self):
68 # Only return the length of the matches so
69 # that this can be used to test if there is
70 # a match.
71 return len(self.children) + len(self.bastards)
72
73 def __iter__(self):
74 return iter(self.children)
75
76 def all(self):
77 return itertools.chain(self.children, self.bastards)
78
79 def append(self, match):
80 if match.dist <= self.threshold:
81 if not match.info_dir_change or self.allow_info_dir_change:
82 self.children.append(match)
83 else:
84 self.bastards.append(match)
85 else:
86 self.bastards.append(match)
87
88 def sort(self):
89 self.children.sort()
90 self.bastards.sort()
91
92
93class AccessMatcher:
94 def __init__(self, perm_maps=None):
95 self.type_penalty = 10
96 self.obj_penalty = 10
97 if perm_maps:
98 self.perm_maps = perm_maps
99 else:
100 self.perm_maps = objectmodel.PermMappings()
101 # We want a change in the information flow direction
102 # to be a strong penalty - stronger than access to
103 # a few unrelated types.
104 self.info_dir_penalty = 100
105
106 def type_distance(self, a, b):
107 if a == b or access.is_idparam(b):
108 return 0
109 else:
110 return -self.type_penalty
111
112
113 def perm_distance(self, av_req, av_prov):
114 # First check that we have enough perms
115 diff = av_req.perms.difference(av_prov.perms)
116
117 if len(diff) != 0:
118 total = self.perm_maps.getdefault_distance(av_req.obj_class, diff)
119 return -total
120 else:
121 diff = av_prov.perms.difference(av_req.perms)
122 return self.perm_maps.getdefault_distance(av_req.obj_class, diff)
123
124 def av_distance(self, req, prov):
125 """Determine the 'distance' between 2 access vectors.
126
127 This function is used to find an access vector that matches
128 a 'required' access. To do this we comput a signed numeric
129 value that indicates how close the req access is to the
130 'provided' access vector. The closer the value is to 0
131 the closer the match, with 0 being an exact match.
132
133 A value over 0 indicates that the prov access vector provides more
134 access than the req (in practice, this means that the source type,
135 target type, and object class is the same and the perms in prov is
136 a superset of those in req.
137
138 A value under 0 indicates that the prov access less - or unrelated
139 - access to the req access. A different type or object class will
140 result in a very low value.
141
142 The values other than 0 should only be interpreted relative to
143 one another - they have no exact meaning and are likely to
144 change.
145
146 Params:
147 req - [AccessVector] The access that is required. This is the
148 access being matched.
149 prov - [AccessVector] The access provided. This is the potential
150 match that is being evaluated for req.
151 Returns:
152 0 : Exact match between the acess vectors.
153
154 < 0 : The prov av does not provide all of the access in req.
155 A smaller value indicates that the access is further.
156
157 > 0 : The prov av provides more access than req. The larger
158 the value the more access over req.
159 """
160 # FUTURE - this is _very_ expensive and probably needs some
161 # thorough performance work. This version is meant to give
162 # meaningful results relatively simply.
163 dist = 0
164
165 # Get the difference between the types. The addition is safe
166 # here because type_distance only returns 0 or negative.
167 dist += self.type_distance(req.src_type, prov.src_type)
168 dist += self.type_distance(req.tgt_type, prov.tgt_type)
169
170 # Object class distance
171 if req.obj_class != prov.obj_class and not access.is_idparam(prov.obj_class):
172 dist -= self.obj_penalty
173
174 # Permission distance
175
176 # If this av doesn't have a matching source type, target type, and object class
177 # count all of the permissions against it. Otherwise determine the perm
178 # distance and dir.
179 if dist < 0:
180 pdist = self.perm_maps.getdefault_distance(prov.obj_class, prov.perms)
181 else:
182 pdist = self.perm_distance(req, prov)
183
184 # Combine the perm and other distance
185 if dist < 0:
186 if pdist < 0:
187 return dist + pdist
188 else:
189 return dist - pdist
190 elif dist >= 0:
191 if pdist < 0:
192 return pdist - dist
193 else:
194 return dist + pdist
195
196 def av_set_match(self, av_set, av):
197 """
198
199 """
200 dist = None
201
202 # Get the distance for each access vector
203 for x in av_set:
204 tmp = self.av_distance(av, x)
205 if dist is None:
206 dist = tmp
207 elif tmp >= 0:
208 if dist >= 0:
209 dist += tmp
210 else:
211 dist = tmp + -dist
212 else:
213 if dist < 0:
214 dist += tmp
215 else:
216 dist -= tmp
217
218 # Penalize for information flow - we want to prevent the
219 # addition of a write if the requested is read none. We are
220 # much less concerned about the reverse.
221 av_dir = self.perm_maps.getdefault_direction(av.obj_class, av.perms)
222
223 if av_set.info_dir is None:
224 av_set.info_dir = objectmodel.FLOW_NONE
225 for x in av_set:
226 av_set.info_dir = av_set.info_dir | \
227 self.perm_maps.getdefault_direction(x.obj_class, x.perms)
228 if (av_dir & objectmodel.FLOW_WRITE == 0) and (av_set.info_dir & objectmodel.FLOW_WRITE):
229 if dist < 0:
230 dist -= self.info_dir_penalty
231 else:
232 dist += self.info_dir_penalty
233
234 return dist
235
236 def search_ifs(self, ifset, av, match_list):
237 match_list.av = av
238 for iv in itertools.chain(ifset.tgt_type_all,
239 ifset.tgt_type_map.get(av.tgt_type, [])):
240 if not iv.enabled:
241 #print "iv %s not enabled" % iv.name
242 continue
243
244 dist = self.av_set_match(iv.access, av)
245 if dist >= 0:
246 m = Match(iv, dist)
247 match_list.append(m)
248
249
250 match_list.sort()
251
252