| # Copyright 2014-2016, Tresys Technology, LLC |
| # |
| # This file is part of SETools. |
| # |
| # SETools is free software: you can redistribute it and/or modify |
| # it under the terms of the GNU Lesser General Public License as |
| # published by the Free Software Foundation, either version 2.1 of |
| # the License, or (at your option) any later version. |
| # |
| # SETools is distributed in the hope that it will be useful, |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| # GNU Lesser General Public License for more details. |
| # |
| # You should have received a copy of the GNU Lesser General Public |
| # License along with SETools. If not, see |
| # <http://www.gnu.org/licenses/>. |
| # |
| # pylint: disable=protected-access |
| import itertools |
| |
| from . import exception |
| from . import qpol |
| from . import symbol |
| |
| # qpol does not expose an equivalent of a sensitivity declaration. |
| # qpol_level_t is equivalent to the level declaration: |
| # level s0:c0.c1023; |
| |
| # qpol_mls_level_t represents a level as used in contexts, |
| # such as range_transitions or labeling statements such as |
| # portcon and nodecon. |
| |
| # Here qpol_level_t is also used for MLSSensitivity |
| # since it has the sensitivity name, dominance, and there |
| # is a 1:1 correspondence between the sensitivity declarations |
| # and level declarations. |
| |
| # Hashing has to be handled below because the qpol references, |
| # normally used for a hash key, are not the same for multiple |
| # instances of the same object (except for level decl). |
| |
| |
| def enabled(policy): |
| """Determine if MLS is enabled.""" |
| return bool(policy.capability(qpol.QPOL_CAP_MLS)) |
| |
| |
| def category_factory(policy, sym): |
| """Factory function for creating MLS category objects.""" |
| |
| if not enabled(policy): |
| raise exception.MLSDisabled |
| |
| if isinstance(sym, Category): |
| assert sym.policy == policy |
| return sym |
| elif isinstance(sym, qpol.qpol_cat_t): |
| if sym.isalias(policy): |
| raise TypeError("{0} is an alias".format(sym.name(policy))) |
| |
| return Category(policy, sym) |
| |
| try: |
| return Category(policy, qpol.qpol_cat_t(policy, str(sym))) |
| except ValueError: |
| raise exception.InvalidCategory("{0} is not a valid category".format(sym)) |
| |
| |
| def sensitivity_factory(policy, sym): |
| """Factory function for creating MLS sensitivity objects.""" |
| |
| if not enabled(policy): |
| raise exception.MLSDisabled |
| |
| if isinstance(sym, Sensitivity): |
| assert sym.policy == policy |
| return sym |
| elif isinstance(sym, qpol.qpol_level_t): |
| if sym.isalias(policy): |
| raise TypeError("{0} is an alias".format(sym.name(policy))) |
| |
| return Sensitivity(policy, sym) |
| |
| try: |
| return Sensitivity(policy, qpol.qpol_level_t(policy, str(sym))) |
| except ValueError: |
| raise exception.InvalidSensitivity("{0} is not a valid sensitivity".format(sym)) |
| |
| |
| def level_factory(policy, sym): |
| """ |
| Factory function for creating MLS level objects (e.g. levels used |
| in contexts of labeling statements) |
| """ |
| |
| if not enabled(policy): |
| raise exception.MLSDisabled |
| |
| if isinstance(sym, Level): |
| assert sym.policy == policy |
| return sym |
| elif isinstance(sym, qpol.qpol_mls_level_t): |
| return Level(policy, sym) |
| |
| sens_split = str(sym).split(":") |
| |
| sens = sens_split[0] |
| try: |
| semantic_level = qpol.qpol_semantic_level_t(policy, sens) |
| except ValueError: |
| raise exception.InvalidLevel("{0} is invalid ({1} is not a valid sensitivity)". |
| format(sym, sens)) |
| |
| try: |
| cats = sens_split[1] |
| except IndexError: |
| pass |
| else: |
| for group in cats.split(","): |
| catrange = group.split(".") |
| |
| if len(catrange) == 2: |
| try: |
| semantic_level.add_cats(policy, catrange[0], catrange[1]) |
| except ValueError: |
| raise exception.InvalidLevel( |
| "{0} is invalid ({1} is not a valid category range)".format(sym, group)) |
| elif len(catrange) == 1: |
| try: |
| semantic_level.add_cats(policy, catrange[0], catrange[0]) |
| except ValueError: |
| raise exception.InvalidLevel("{0} is invalid ({1} is not a valid category)". |
| format(sym, group)) |
| else: |
| raise exception.InvalidLevel("{0} is invalid (level parsing error)".format(sym)) |
| |
| # convert to level object |
| try: |
| policy_level = qpol.qpol_mls_level_t(policy, semantic_level) |
| except ValueError: |
| raise exception.InvalidLevel( |
| "{0} is invalid (one or more categories are not associated with the sensitivity)". |
| format(sym)) |
| |
| return Level(policy, policy_level) |
| |
| |
| def level_decl_factory(policy, sym): |
| """ |
| Factory function for creating MLS level declaration objects. |
| (level statements) Lookups are only by sensitivity name. |
| """ |
| |
| if not enabled(policy): |
| raise exception.MLSDisabled |
| |
| if isinstance(sym, LevelDecl): |
| assert sym.policy == policy |
| return sym |
| elif isinstance(sym, qpol.qpol_level_t): |
| if sym.isalias(policy): |
| raise TypeError("{0} is an alias".format(sym.name(policy))) |
| |
| return LevelDecl(policy, sym) |
| |
| try: |
| return LevelDecl(policy, qpol.qpol_level_t(policy, str(sym))) |
| except ValueError: |
| raise exception.InvalidLevelDecl("{0} is not a valid sensitivity".format(sym)) |
| |
| |
| def range_factory(policy, sym): |
| """Factory function for creating MLS range objects.""" |
| |
| if not enabled(policy): |
| raise exception.MLSDisabled |
| |
| if isinstance(sym, Range): |
| assert sym.policy == policy |
| return sym |
| elif isinstance(sym, qpol.qpol_mls_range_t): |
| return Range(policy, sym) |
| |
| # build range: |
| levels = str(sym).split("-") |
| |
| # strip() levels to handle ranges with spaces in them, |
| # e.g. s0:c1 - s0:c0.c255 |
| try: |
| low = level_factory(policy, levels[0].strip()) |
| except exception.InvalidLevel as ex: |
| raise exception.InvalidRange("{0} is not a valid range ({1}).".format(sym, ex)) |
| |
| try: |
| high = level_factory(policy, levels[1].strip()) |
| except exception.InvalidLevel as ex: |
| raise exception.InvalidRange("{0} is not a valid range ({1}).".format(sym, ex)) |
| except IndexError: |
| high = low |
| |
| # convert to range object |
| try: |
| policy_range = qpol.qpol_mls_range_t(policy, low.qpol_symbol, high.qpol_symbol) |
| except ValueError: |
| raise exception.InvalidRange("{0} is not a valid range ({1} is not dominated by {2})". |
| format(sym, low, high)) |
| |
| return Range(policy, policy_range) |
| |
| |
| class BaseMLSComponent(symbol.PolicySymbol): |
| |
| """Base class for sensitivities and categories.""" |
| |
| @property |
| def _value(self): |
| """ |
| The value of the component. |
| |
| This is a low-level policy detail exposed for internal use only. |
| """ |
| return self.qpol_symbol.value(self.policy) |
| |
| def aliases(self): |
| """Generator that yields all aliases for this category.""" |
| |
| for alias in self.qpol_symbol.alias_iter(self.policy): |
| yield alias |
| |
| |
| class Category(BaseMLSComponent): |
| |
| """An MLS category.""" |
| |
| def statement(self): |
| aliases = list(self.aliases()) |
| stmt = "category {0}".format(self) |
| if aliases: |
| if len(aliases) > 1: |
| stmt += " alias {{ {0} }}".format(' '.join(aliases)) |
| else: |
| stmt += " alias {0}".format(aliases[0]) |
| stmt += ";" |
| return stmt |
| |
| def __lt__(self, other): |
| """Comparison based on their index instead of their names.""" |
| return self._value < other._value |
| |
| |
| class Sensitivity(BaseMLSComponent): |
| |
| """An MLS sensitivity""" |
| |
| def __ge__(self, other): |
| return self._value >= other._value |
| |
| def __gt__(self, other): |
| return self._value > other._value |
| |
| def __le__(self, other): |
| return self._value <= other._value |
| |
| def __lt__(self, other): |
| return self._value < other._value |
| |
| def statement(self): |
| aliases = list(self.aliases()) |
| stmt = "sensitivity {0}".format(self) |
| if aliases: |
| if len(aliases) > 1: |
| stmt += " alias {{ {0} }}".format(' '.join(aliases)) |
| else: |
| stmt += " alias {0}".format(aliases[0]) |
| stmt += ";" |
| return stmt |
| |
| |
| class BaseMLSLevel(symbol.PolicySymbol): |
| |
| """Base class for MLS levels.""" |
| |
| def __str__(self): |
| lvl = str(self.sensitivity) |
| |
| # sort by policy declaration order |
| cats = sorted(self.categories(), key=lambda k: k._value) |
| |
| if cats: |
| # generate short category notation |
| shortlist = [] |
| for _, i in itertools.groupby(cats, key=lambda k, |
| c=itertools.count(): k._value - next(c)): |
| group = list(i) |
| if len(group) > 1: |
| shortlist.append("{0}.{1}".format(group[0], group[-1])) |
| else: |
| shortlist.append(str(group[0])) |
| |
| lvl += ":" + ','.join(shortlist) |
| |
| return lvl |
| |
| @property |
| def sensitivity(self): |
| raise NotImplementedError |
| |
| def categories(self): |
| """ |
| Generator that yields all individual categories for this level. |
| All categories are yielded, not a compact notation such as |
| c0.c255 |
| """ |
| |
| for cat in self.qpol_symbol.cat_iter(self.policy): |
| yield category_factory(self.policy, cat) |
| |
| |
| class LevelDecl(BaseMLSLevel): |
| |
| """ |
| The declaration statement for MLS levels, e.g: |
| |
| level s7:c0.c1023; |
| """ |
| |
| def __hash__(self): |
| return hash(self.sensitivity) |
| |
| # below comparisons are only based on sensitivity |
| # dominance since, in this context, the allowable |
| # category set is being defined for the level. |
| # object type is asserted here because this cannot |
| # be compared to a Level instance. |
| |
| def __eq__(self, other): |
| assert not isinstance(other, Level), "Levels cannot be compared to level declarations" |
| |
| try: |
| return self.sensitivity == other.sensitivity |
| except AttributeError: |
| return str(self) == str(other) |
| |
| def __ge__(self, other): |
| assert not isinstance(other, Level), "Levels cannot be compared to level declarations" |
| return self.sensitivity >= other.sensitivity |
| |
| def __gt__(self, other): |
| assert not isinstance(other, Level), "Levels cannot be compared to level declarations" |
| return self.sensitivity > other.sensitivity |
| |
| def __le__(self, other): |
| assert not isinstance(other, Level), "Levels cannot be compared to level declarations" |
| return self.sensitivity <= other.sensitivity |
| |
| def __lt__(self, other): |
| assert not isinstance(other, Level), "Levels cannot be compared to level declarations" |
| return self.sensitivity < other.sensitivity |
| |
| @property |
| def sensitivity(self): |
| """The sensitivity of the level.""" |
| # since the qpol symbol for levels is also used for |
| # MLSSensitivity objects, use self's qpol symbol |
| return sensitivity_factory(self.policy, self.qpol_symbol) |
| |
| def statement(self): |
| return "level {0};".format(self) |
| |
| |
| class Level(BaseMLSLevel): |
| |
| """An MLS level used in contexts.""" |
| |
| def __hash__(self): |
| return hash(str(self)) |
| |
| def __eq__(self, other): |
| try: |
| othercats = set(other.categories()) |
| except AttributeError: |
| return str(self) == str(other) |
| else: |
| selfcats = set(self.categories()) |
| return self.sensitivity == other.sensitivity and selfcats == othercats |
| |
| def __ge__(self, other): |
| """Dom operator.""" |
| selfcats = set(self.categories()) |
| othercats = set(other.categories()) |
| return self.sensitivity >= other.sensitivity and selfcats >= othercats |
| |
| def __gt__(self, other): |
| selfcats = set(self.categories()) |
| othercats = set(other.categories()) |
| return ((self.sensitivity > other.sensitivity and selfcats >= othercats) or |
| (self.sensitivity >= other.sensitivity and selfcats > othercats)) |
| |
| def __le__(self, other): |
| """Domby operator.""" |
| selfcats = set(self.categories()) |
| othercats = set(other.categories()) |
| return self.sensitivity <= other.sensitivity and selfcats <= othercats |
| |
| def __lt__(self, other): |
| selfcats = set(self.categories()) |
| othercats = set(other.categories()) |
| return ((self.sensitivity < other.sensitivity and selfcats <= othercats) or |
| (self.sensitivity <= other.sensitivity and selfcats < othercats)) |
| |
| def __xor__(self, other): |
| """Incomp operator.""" |
| return not (self >= other or self <= other) |
| |
| @property |
| def sensitivity(self): |
| """The sensitivity of the level.""" |
| return sensitivity_factory(self.policy, self.qpol_symbol.sens_name(self.policy)) |
| |
| def statement(self): |
| raise exception.NoStatement |
| |
| |
| class Range(symbol.PolicySymbol): |
| |
| """An MLS range""" |
| |
| def __str__(self): |
| high = self.high |
| low = self.low |
| if high == low: |
| return str(low) |
| |
| return "{0} - {1}".format(low, high) |
| |
| def __hash__(self): |
| return hash(str(self)) |
| |
| def __eq__(self, other): |
| try: |
| return self.low == other.low and self.high == other.high |
| except AttributeError: |
| # remove all spaces in the string representations |
| # to handle cases where the other object does not |
| # have spaces around the '-' |
| other_str = str(other).replace(" ", "") |
| self_str = str(self).replace(" ", "") |
| return self_str == other_str |
| |
| def __contains__(self, other): |
| return self.low <= other <= self.high |
| |
| @property |
| def high(self): |
| """The high end/clearance level of this range.""" |
| return level_factory(self.policy, self.qpol_symbol.high_level(self.policy)) |
| |
| @property |
| def low(self): |
| """The low end/current level of this range.""" |
| return level_factory(self.policy, self.qpol_symbol.low_level(self.policy)) |
| |
| def statement(self): |
| raise exception.NoStatement |