Home | History | Annotate | Download | only in policyrep
      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