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 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 253