1 # Copyright 2014-2016, Tresys Technology, LLC 2 # 3 # This file is part of SETools. 4 # 5 # SETools is free software: you can redistribute it and/or modify 6 # it under the terms of the GNU Lesser General Public License as 7 # published by the Free Software Foundation, either version 2.1 of 8 # the License, or (at your option) any later version. 9 # 10 # SETools 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 Lesser General Public License for more details. 14 # 15 # You should have received a copy of the GNU Lesser General Public 16 # License along with SETools. If not, see 17 # <http://www.gnu.org/licenses/>. 18 # 19 # pylint: disable=protected-access 20 import itertools 21 22 from . import exception 23 from . import qpol 24 from . import symbol 25 26 # qpol does not expose an equivalent of a sensitivity declaration. 27 # qpol_level_t is equivalent to the level declaration: 28 # level s0:c0.c1023; 29 30 # qpol_mls_level_t represents a level as used in contexts, 31 # such as range_transitions or labeling statements such as 32 # portcon and nodecon. 33 34 # Here qpol_level_t is also used for MLSSensitivity 35 # since it has the sensitivity name, dominance, and there 36 # is a 1:1 correspondence between the sensitivity declarations 37 # and level declarations. 38 39 # Hashing has to be handled below because the qpol references, 40 # normally used for a hash key, are not the same for multiple 41 # instances of the same object (except for level decl). 42 43 44 def enabled(policy): 45 """Determine if MLS is enabled.""" 46 return bool(policy.capability(qpol.QPOL_CAP_MLS)) 47 48 49 def category_factory(policy, sym): 50 """Factory function for creating MLS category objects.""" 51 52 if not enabled(policy): 53 raise exception.MLSDisabled 54 55 if isinstance(sym, Category): 56 assert sym.policy == policy 57 return sym 58 elif isinstance(sym, qpol.qpol_cat_t): 59 if sym.isalias(policy): 60 raise TypeError("{0} is an alias".format(sym.name(policy))) 61 62 return Category(policy, sym) 63 64 try: 65 return Category(policy, qpol.qpol_cat_t(policy, str(sym))) 66 except ValueError: 67 raise exception.InvalidCategory("{0} is not a valid category".format(sym)) 68 69 70 def sensitivity_factory(policy, sym): 71 """Factory function for creating MLS sensitivity objects.""" 72 73 if not enabled(policy): 74 raise exception.MLSDisabled 75 76 if isinstance(sym, Sensitivity): 77 assert sym.policy == policy 78 return sym 79 elif isinstance(sym, qpol.qpol_level_t): 80 if sym.isalias(policy): 81 raise TypeError("{0} is an alias".format(sym.name(policy))) 82 83 return Sensitivity(policy, sym) 84 85 try: 86 return Sensitivity(policy, qpol.qpol_level_t(policy, str(sym))) 87 except ValueError: 88 raise exception.InvalidSensitivity("{0} is not a valid sensitivity".format(sym)) 89 90 91 def level_factory(policy, sym): 92 """ 93 Factory function for creating MLS level objects (e.g. levels used 94 in contexts of labeling statements) 95 """ 96 97 if not enabled(policy): 98 raise exception.MLSDisabled 99 100 if isinstance(sym, Level): 101 assert sym.policy == policy 102 return sym 103 elif isinstance(sym, qpol.qpol_mls_level_t): 104 return Level(policy, sym) 105 106 sens_split = str(sym).split(":") 107 108 sens = sens_split[0] 109 try: 110 semantic_level = qpol.qpol_semantic_level_t(policy, sens) 111 except ValueError: 112 raise exception.InvalidLevel("{0} is not a valid level ({1} is not a valid sensitivity)". 113 format(sym, sens)) 114 115 try: 116 cats = sens_split[1] 117 except IndexError: 118 pass 119 else: 120 for group in cats.split(","): 121 catrange = group.split(".") 122 123 if len(catrange) == 2: 124 try: 125 semantic_level.add_cats(policy, catrange[0], catrange[1]) 126 except ValueError: 127 raise exception.InvalidLevel( 128 "{0} is not a valid level ({1} is not a valid category range)". 129 format(sym, group)) 130 elif len(catrange) == 1: 131 try: 132 semantic_level.add_cats(policy, catrange[0], catrange[0]) 133 except ValueError: 134 raise exception.InvalidLevel( 135 "{0} is not a valid level ({1} is not a valid category)".format(sym, group)) 136 else: 137 raise exception.InvalidLevel( 138 "{0} is not a valid level (level parsing error)".format(sym)) 139 140 # convert to level object 141 try: 142 policy_level = qpol.qpol_mls_level_t(policy, semantic_level) 143 except ValueError: 144 raise exception.InvalidLevel( 145 "{0} is not a valid level (one or more categories are not associated with the " 146 "sensitivity)".format(sym)) 147 148 return Level(policy, policy_level) 149 150 151 def level_decl_factory(policy, sym): 152 """ 153 Factory function for creating MLS level declaration objects. 154 (level statements) Lookups are only by sensitivity name. 155 """ 156 157 if not enabled(policy): 158 raise exception.MLSDisabled 159 160 if isinstance(sym, LevelDecl): 161 assert sym.policy == policy 162 return sym 163 elif isinstance(sym, qpol.qpol_level_t): 164 if sym.isalias(policy): 165 raise TypeError("{0} is an alias".format(sym.name(policy))) 166 167 return LevelDecl(policy, sym) 168 169 try: 170 return LevelDecl(policy, qpol.qpol_level_t(policy, str(sym))) 171 except ValueError: 172 raise exception.InvalidLevelDecl("{0} is not a valid sensitivity".format(sym)) 173 174 175 def range_factory(policy, sym): 176 """Factory function for creating MLS range objects.""" 177 178 if not enabled(policy): 179 raise exception.MLSDisabled 180 181 if isinstance(sym, Range): 182 assert sym.policy == policy 183 return sym 184 elif isinstance(sym, qpol.qpol_mls_range_t): 185 return Range(policy, sym) 186 187 # build range: 188 levels = str(sym).split("-") 189 190 # strip() levels to handle ranges with spaces in them, 191 # e.g. s0:c1 - s0:c0.c255 192 try: 193 low = level_factory(policy, levels[0].strip()) 194 except exception.InvalidLevel as ex: 195 raise exception.InvalidRange("{0} is not a valid range ({1}).".format(sym, ex)) 196 197 try: 198 high = level_factory(policy, levels[1].strip()) 199 except exception.InvalidLevel as ex: 200 raise exception.InvalidRange("{0} is not a valid range ({1}).".format(sym, ex)) 201 except IndexError: 202 high = low 203 204 # convert to range object 205 try: 206 policy_range = qpol.qpol_mls_range_t(policy, low.qpol_symbol, high.qpol_symbol) 207 except ValueError: 208 raise exception.InvalidRange("{0} is not a valid range ({1} is not dominated by {2})". 209 format(sym, low, high)) 210 211 return Range(policy, policy_range) 212 213 214 class BaseMLSComponent(symbol.PolicySymbol): 215 216 """Base class for sensitivities and categories.""" 217 218 @property 219 def _value(self): 220 """ 221 The value of the component. 222 223 This is a low-level policy detail exposed for internal use only. 224 """ 225 return self.qpol_symbol.value(self.policy) 226 227 def aliases(self): 228 """Generator that yields all aliases for this category.""" 229 230 for alias in self.qpol_symbol.alias_iter(self.policy): 231 yield alias 232 233 234 class Category(BaseMLSComponent): 235 236 """An MLS category.""" 237 238 def statement(self): 239 aliases = list(self.aliases()) 240 stmt = "category {0}".format(self) 241 if aliases: 242 if len(aliases) > 1: 243 stmt += " alias {{ {0} }}".format(' '.join(aliases)) 244 else: 245 stmt += " alias {0}".format(aliases[0]) 246 stmt += ";" 247 return stmt 248 249 def __lt__(self, other): 250 """Comparison based on their index instead of their names.""" 251 return self._value < other._value 252 253 254 class Sensitivity(BaseMLSComponent): 255 256 """An MLS sensitivity""" 257 258 def __ge__(self, other): 259 return self._value >= other._value 260 261 def __gt__(self, other): 262 return self._value > other._value 263 264 def __le__(self, other): 265 return self._value <= other._value 266 267 def __lt__(self, other): 268 return self._value < other._value 269 270 def statement(self): 271 aliases = list(self.aliases()) 272 stmt = "sensitivity {0}".format(self) 273 if aliases: 274 if len(aliases) > 1: 275 stmt += " alias {{ {0} }}".format(' '.join(aliases)) 276 else: 277 stmt += " alias {0}".format(aliases[0]) 278 stmt += ";" 279 return stmt 280 281 282 class BaseMLSLevel(symbol.PolicySymbol): 283 284 """Base class for MLS levels.""" 285 286 def __str__(self): 287 lvl = str(self.sensitivity) 288 289 # sort by policy declaration order 290 cats = sorted(self.categories(), key=lambda k: k._value) 291 292 if cats: 293 # generate short category notation 294 shortlist = [] 295 for _, i in itertools.groupby(cats, key=lambda k, 296 c=itertools.count(): k._value - next(c)): 297 group = list(i) 298 if len(group) > 1: 299 shortlist.append("{0}.{1}".format(group[0], group[-1])) 300 else: 301 shortlist.append(str(group[0])) 302 303 lvl += ":" + ','.join(shortlist) 304 305 return lvl 306 307 @property 308 def sensitivity(self): 309 raise NotImplementedError 310 311 def categories(self): 312 """ 313 Generator that yields all individual categories for this level. 314 All categories are yielded, not a compact notation such as 315 c0.c255 316 """ 317 318 for cat in self.qpol_symbol.cat_iter(self.policy): 319 yield category_factory(self.policy, cat) 320 321 322 class LevelDecl(BaseMLSLevel): 323 324 """ 325 The declaration statement for MLS levels, e.g: 326 327 level s7:c0.c1023; 328 """ 329 330 def __hash__(self): 331 return hash(self.sensitivity) 332 333 # below comparisons are only based on sensitivity 334 # dominance since, in this context, the allowable 335 # category set is being defined for the level. 336 # object type is asserted here because this cannot 337 # be compared to a Level instance. 338 339 def __eq__(self, other): 340 assert not isinstance(other, Level), "Levels cannot be compared to level declarations" 341 342 try: 343 return self.sensitivity == other.sensitivity 344 except AttributeError: 345 return str(self) == str(other) 346 347 def __ge__(self, other): 348 assert not isinstance(other, Level), "Levels cannot be compared to level declarations" 349 return self.sensitivity >= other.sensitivity 350 351 def __gt__(self, other): 352 assert not isinstance(other, Level), "Levels cannot be compared to level declarations" 353 return self.sensitivity > other.sensitivity 354 355 def __le__(self, other): 356 assert not isinstance(other, Level), "Levels cannot be compared to level declarations" 357 return self.sensitivity <= other.sensitivity 358 359 def __lt__(self, other): 360 assert not isinstance(other, Level), "Levels cannot be compared to level declarations" 361 return self.sensitivity < other.sensitivity 362 363 @property 364 def sensitivity(self): 365 """The sensitivity of the level.""" 366 # since the qpol symbol for levels is also used for 367 # MLSSensitivity objects, use self's qpol symbol 368 return sensitivity_factory(self.policy, self.qpol_symbol) 369 370 def statement(self): 371 return "level {0};".format(self) 372 373 374 class Level(BaseMLSLevel): 375 376 """An MLS level used in contexts.""" 377 378 def __hash__(self): 379 return hash(str(self)) 380 381 def __eq__(self, other): 382 try: 383 othercats = set(other.categories()) 384 except AttributeError: 385 return str(self) == str(other) 386 else: 387 selfcats = set(self.categories()) 388 return self.sensitivity == other.sensitivity and selfcats == othercats 389 390 def __ge__(self, other): 391 """Dom operator.""" 392 selfcats = set(self.categories()) 393 othercats = set(other.categories()) 394 return self.sensitivity >= other.sensitivity and selfcats >= othercats 395 396 def __gt__(self, other): 397 selfcats = set(self.categories()) 398 othercats = set(other.categories()) 399 return ((self.sensitivity > other.sensitivity and selfcats >= othercats) or 400 (self.sensitivity >= other.sensitivity and selfcats > othercats)) 401 402 def __le__(self, other): 403 """Domby operator.""" 404 selfcats = set(self.categories()) 405 othercats = set(other.categories()) 406 return self.sensitivity <= other.sensitivity and selfcats <= othercats 407 408 def __lt__(self, other): 409 selfcats = set(self.categories()) 410 othercats = set(other.categories()) 411 return ((self.sensitivity < other.sensitivity and selfcats <= othercats) or 412 (self.sensitivity <= other.sensitivity and selfcats < othercats)) 413 414 def __xor__(self, other): 415 """Incomp operator.""" 416 return not (self >= other or self <= other) 417 418 @property 419 def sensitivity(self): 420 """The sensitivity of the level.""" 421 return sensitivity_factory(self.policy, self.qpol_symbol.sens_name(self.policy)) 422 423 def statement(self): 424 raise exception.NoStatement 425 426 427 class Range(symbol.PolicySymbol): 428 429 """An MLS range""" 430 431 def __str__(self): 432 high = self.high 433 low = self.low 434 if high == low: 435 return str(low) 436 437 return "{0} - {1}".format(low, high) 438 439 def __hash__(self): 440 return hash(str(self)) 441 442 def __eq__(self, other): 443 try: 444 return self.low == other.low and self.high == other.high 445 except AttributeError: 446 # remove all spaces in the string representations 447 # to handle cases where the other object does not 448 # have spaces around the '-' 449 other_str = str(other).replace(" ", "") 450 self_str = str(self).replace(" ", "") 451 return self_str == other_str 452 453 def __contains__(self, other): 454 return self.low <= other <= self.high 455 456 @property 457 def high(self): 458 """The high end/clearance level of this range.""" 459 return level_factory(self.policy, self.qpol_symbol.high_level(self.policy)) 460 461 @property 462 def low(self): 463 """The low end/current level of this range.""" 464 return level_factory(self.policy, self.qpol_symbol.low_level(self.policy)) 465 466 def statement(self): 467 raise exception.NoStatement 468