# Copyright 2014-2015, 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/>. # from . import exception from . import qpol from . import role from . import symbol from . import objclass from . import typeattr from . import user def _is_mls(policy, sym): """Determine if this is a regular or MLS constraint/validatetrans.""" # this can only be determined by inspecting the expression. for expr_node in sym.expr_iter(policy): sym_type = expr_node.sym_type(policy) expr_type = expr_node.expr_type(policy) if expr_type == qpol.QPOL_CEXPR_TYPE_ATTR and sym_type >= qpol.QPOL_CEXPR_SYM_L1L2: return True return False def validate_ruletype(t): """Validate constraint rule types.""" if t not in ["constrain", "mlsconstrain", "validatetrans", "mlsvalidatetrans"]: raise exception.InvalidConstraintType("{0} is not a valid constraint type.".format(t)) return t def constraint_factory(policy, sym): """Factory function for creating constraint objects.""" try: if _is_mls(policy, sym): if isinstance(sym, qpol.qpol_constraint_t): return Constraint(policy, sym, "mlsconstrain") else: return Validatetrans(policy, sym, "mlsvalidatetrans") else: if isinstance(sym, qpol.qpol_constraint_t): return Constraint(policy, sym, "constrain") else: return Validatetrans(policy, sym, "validatetrans") except AttributeError: raise TypeError("Constraints cannot be looked-up.") class BaseConstraint(symbol.PolicySymbol): """Base class for constraint rules.""" _expr_type_to_text = { qpol.QPOL_CEXPR_TYPE_NOT: "not", qpol.QPOL_CEXPR_TYPE_AND: "and", qpol.QPOL_CEXPR_TYPE_OR: "\n\tor"} _expr_op_to_text = { qpol.QPOL_CEXPR_OP_EQ: "==", qpol.QPOL_CEXPR_OP_NEQ: "!=", qpol.QPOL_CEXPR_OP_DOM: "dom", qpol.QPOL_CEXPR_OP_DOMBY: "domby", qpol.QPOL_CEXPR_OP_INCOMP: "incomp"} _sym_to_text = { qpol.QPOL_CEXPR_SYM_USER: "u1", qpol.QPOL_CEXPR_SYM_ROLE: "r1", qpol.QPOL_CEXPR_SYM_TYPE: "t1", qpol.QPOL_CEXPR_SYM_USER + qpol.QPOL_CEXPR_SYM_TARGET: "u2", qpol.QPOL_CEXPR_SYM_ROLE + qpol.QPOL_CEXPR_SYM_TARGET: "r2", qpol.QPOL_CEXPR_SYM_TYPE + qpol.QPOL_CEXPR_SYM_TARGET: "t2", qpol.QPOL_CEXPR_SYM_USER + qpol.QPOL_CEXPR_SYM_XTARGET: "u3", qpol.QPOL_CEXPR_SYM_ROLE + qpol.QPOL_CEXPR_SYM_XTARGET: "r3", qpol.QPOL_CEXPR_SYM_TYPE + qpol.QPOL_CEXPR_SYM_XTARGET: "t3", qpol.QPOL_CEXPR_SYM_L1L2: "l1", qpol.QPOL_CEXPR_SYM_L1H2: "l1", qpol.QPOL_CEXPR_SYM_H1L2: "h1", qpol.QPOL_CEXPR_SYM_H1H2: "h1", qpol.QPOL_CEXPR_SYM_L1H1: "l1", qpol.QPOL_CEXPR_SYM_L2H2: "l2", qpol.QPOL_CEXPR_SYM_L1L2 + qpol.QPOL_CEXPR_SYM_TARGET: "l2", qpol.QPOL_CEXPR_SYM_L1H2 + qpol.QPOL_CEXPR_SYM_TARGET: "h2", qpol.QPOL_CEXPR_SYM_H1L2 + qpol.QPOL_CEXPR_SYM_TARGET: "l2", qpol.QPOL_CEXPR_SYM_H1H2 + qpol.QPOL_CEXPR_SYM_TARGET: "h2", qpol.QPOL_CEXPR_SYM_L1H1 + qpol.QPOL_CEXPR_SYM_TARGET: "h1", qpol.QPOL_CEXPR_SYM_L2H2 + qpol.QPOL_CEXPR_SYM_TARGET: "h2"} # Boolean operators _expr_type_to_precedence = { qpol.QPOL_CEXPR_TYPE_NOT: 3, qpol.QPOL_CEXPR_TYPE_AND: 2, qpol.QPOL_CEXPR_TYPE_OR: 1} # Logical operators have the same precedence _logical_op_precedence = 4 def __init__(self, policy, qpol_symbol, ruletype): symbol.PolicySymbol.__init__(self, policy, qpol_symbol) self.ruletype = ruletype def __str__(self): raise NotImplementedError def _build_expression(self): # qpol representation is in postfix notation. This code # converts it to infix notation. Parentheses are added # to ensure correct expressions, though they may end up # being overused. Set previous operator at start to the # highest precedence (op) so if there is a single binary # operator, no parentheses are output stack = [] prev_op_precedence = self._logical_op_precedence for expr_node in self.qpol_symbol.expr_iter(self.policy): op = expr_node.op(self.policy) sym_type = expr_node.sym_type(self.policy) expr_type = expr_node.expr_type(self.policy) if expr_type == qpol.QPOL_CEXPR_TYPE_ATTR: # logical operator with symbol (e.g. u1 == u2) operand1 = self._sym_to_text[sym_type] operand2 = self._sym_to_text[sym_type + qpol.QPOL_CEXPR_SYM_TARGET] operator = self._expr_op_to_text[op] stack.append([operand1, operator, operand2]) prev_op_precedence = self._logical_op_precedence elif expr_type == qpol.QPOL_CEXPR_TYPE_NAMES: # logical operator with type or attribute list (e.g. t1 == { spam_t eggs_t }) operand1 = self._sym_to_text[sym_type] operator = self._expr_op_to_text[op] names = list(expr_node.names_iter(self.policy)) if not names: operand2 = "<empty set>" elif len(names) == 1: operand2 = names[0] else: operand2 = "{{ {0} }}".format(' '.join(names)) stack.append([operand1, operator, operand2]) prev_op_precedence = self._logical_op_precedence elif expr_type == qpol.QPOL_CEXPR_TYPE_NOT: # unary operator (not) operand = stack.pop() operator = self._expr_type_to_text[expr_type] stack.append([operator, "(", operand, ")"]) prev_op_precedence = self._expr_type_to_precedence[expr_type] else: # binary operator (and/or) operand1 = stack.pop() operand2 = stack.pop() operator = self._expr_type_to_text[expr_type] op_precedence = self._expr_type_to_precedence[expr_type] # if previous operator is of higher precedence # no parentheses are needed. if op_precedence < prev_op_precedence: stack.append([operand1, operator, operand2]) else: stack.append(["(", operand1, operator, operand2, ")"]) prev_op_precedence = op_precedence return self.__unwind_subexpression(stack) def _get_symbols(self, syms, factory): """ Internal generator for getting users/roles/types in a constraint expression. Symbols will be yielded multiple times if they appear in the expression multiple times. Parameters: syms List of qpol symbol types. factory The factory function related to these symbols. """ for expr_node in self.qpol_symbol.expr_iter(self.policy): sym_type = expr_node.sym_type(self.policy) expr_type = expr_node.expr_type(self.policy) if expr_type == qpol.QPOL_CEXPR_TYPE_NAMES and sym_type in syms: for s in expr_node.names_iter(self.policy): yield factory(self.policy, s) def __unwind_subexpression(self, expr): ret = [] # do a string.join on sublists (subexpressions) for i in expr: if isinstance(i, list): ret.append(self.__unwind_subexpression(i)) else: ret.append(i) return ' '.join(ret) # There is no levels function as specific # levels cannot be used in expressions, only # the l1, h1, etc. symbols @property def roles(self): """The roles used in the expression.""" role_syms = [qpol.QPOL_CEXPR_SYM_ROLE, qpol.QPOL_CEXPR_SYM_ROLE + qpol.QPOL_CEXPR_SYM_TARGET, qpol.QPOL_CEXPR_SYM_ROLE + qpol.QPOL_CEXPR_SYM_XTARGET] return set(self._get_symbols(role_syms, role.role_factory)) @property def perms(self): raise NotImplementedError def statement(self): return str(self) @property def tclass(self): """Object class for this constraint.""" return objclass.class_factory(self.policy, self.qpol_symbol.object_class(self.policy)) @property def types(self): """The types and type attributes used in the expression.""" type_syms = [qpol.QPOL_CEXPR_SYM_TYPE, qpol.QPOL_CEXPR_SYM_TYPE + qpol.QPOL_CEXPR_SYM_TARGET, qpol.QPOL_CEXPR_SYM_TYPE + qpol.QPOL_CEXPR_SYM_XTARGET] return set(self._get_symbols(type_syms, typeattr.type_or_attr_factory)) @property def users(self): """The users used in the expression.""" user_syms = [qpol.QPOL_CEXPR_SYM_USER, qpol.QPOL_CEXPR_SYM_USER + qpol.QPOL_CEXPR_SYM_TARGET, qpol.QPOL_CEXPR_SYM_USER + qpol.QPOL_CEXPR_SYM_XTARGET] return set(self._get_symbols(user_syms, user.user_factory)) class Constraint(BaseConstraint): """A constraint rule (constrain/mlsconstrain).""" def __str__(self): rule_string = "{0.ruletype} {0.tclass} ".format(self) perms = self.perms if len(perms) > 1: rule_string += "{{ {0} }} (\n".format(' '.join(perms)) else: # convert to list since sets cannot be indexed rule_string += "{0} (\n".format(list(perms)[0]) rule_string += "\t{0}\n);".format(self._build_expression()) return rule_string @property def perms(self): """The constraint's permission set.""" return set(self.qpol_symbol.perm_iter(self.policy)) class Validatetrans(BaseConstraint): """A validatetrans rule (validatetrans/mlsvalidatetrans).""" def __str__(self): return "{0.ruletype} {0.tclass}\n\t{1}\n);".format(self, self._build_expression()) @property def perms(self): raise exception.ConstraintUseError("{0} rules do not have permissions.". format(self.ruletype))