Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sesearch: CIL output #134

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions man/sesearch.1
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ Print version information and exit.
Print additional informational messages.
.IP "--debug"
Enable debugging output.
.IP "-C, --cil"
Set output format to CIL.

.SH EXAMPLE
.nf
Expand Down
3 changes: 2 additions & 1 deletion sesearch
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ parser.add_argument("policy", help="Path to the SELinux policy to search.", narg
parser.add_argument("-v", "--verbose", action="store_true",
help="Print extra informational messages")
parser.add_argument("--debug", action="store_true", dest="debug", help="Enable debugging.")
parser.add_argument("-C", "--cil", action="store_true", dest="gen_cil", help="Enable CIL output.")

rtypes = parser.add_argument_group("TE Rule Types")
rtypes.add_argument("-A", action="store_true", help="Search allow and allowxperm rules.")
Expand Down Expand Up @@ -143,7 +144,7 @@ else:
warnings.simplefilter("ignore")

try:
p = setools.SELinuxPolicy(args.policy)
p = setools.SELinuxPolicy(args.policy, gen_cil=args.gen_cil)
Comment on lines -146 to +147
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CIL is not a property of the policy. It should be a keyword parameter to the statement() method of the individual classes, with a default of the kernel (current) output.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually my first version used a similar method, the flag was stored as PolicyObject::gen_cil, set in sesearch near output stage. The problem I had with that approach was that a lot of effort was needed to make sure the flag was copied to all objects and that gets ugly. I never found out why the output in some case wasn't CIL. Also, statement() method is not used directly in a lot of places but __str__() instead. Perhaps just my poor Python skills are showing.

I'd even argue that while CIL output is indeed not a property of a policy, it's pretty much a global flag. There should never be a need for mixing CIL and kernel output.

But I'll try the format() method next, perhaps the format option gets passed around more easily,

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I hand you a policy.33 with no context whatsoever and ask you if it's source files were CIL, you can't answer because it's not a property of the policy.


if args.tertypes:
terq = setools.TERuleQuery(p,
Expand Down
2 changes: 2 additions & 0 deletions setools/policyrep.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ class ConditionalOperator(PolicyObject):
evaluate: Callable = ...
precedence: int = ...
unary: bool = ...
cil_text: str = ...

class Constraint(BaseConstraint):
perms: frozenset[str] = ...
Expand Down Expand Up @@ -376,6 +377,7 @@ class SELinuxPolicy:
dontaudit_count: int = ...
dontauditxperm_count: int = ...
fs_use_count: int = ...
gen_cil: bool = ...
genfscon_count: int = ...
handle_unknown: "HandleUnknown" = ...
ibendportcon_count: int = ...
Expand Down
123 changes: 77 additions & 46 deletions setools/policyrep/boolcond.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -78,50 +78,77 @@ cdef class Conditional(PolicyObject):
return other in self.booleans

def __str__(self):
# sepol representation is in postfix notation. This code
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here and in other places, where there isn't a statement, it should be handled using format(), also with a default of kernel output. Please support kernel and cil as format options. See setools/dta.py: DomainTransition for an example.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking more about your example output, this is not meant to output a full conditional block, so it should not be rendering a full booleanif/(true/(false block in the cil output.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here and in other places, where there isn't a statement, it should be handled using format(), also with a default of kernel output. Please support kernel and cil as format options. See setools/dta.py: DomainTransition for an example.

That looks nice, thanks for the pointer.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking more about your example output, this is not meant to output a full conditional block, so it should not be rendering a full booleanif/(true/(false block in the cil output.

The purpose and use case of the CIL output flag (which I should have stated in the patch description) is that when the output is set to CIL, I can copy and use the text mechanically without modification in other CIL policies. It's also possible to compare the original CIL policy and the output of sesearch. If you invent a new syntax which is not CIL, the output would need to be converted to CIL somehow (perhaps with another tool) and the flag would become much less useful. The same problem exists in the kernel syntax version, the output is not valid KPL syntax either.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same problem exists in the kernel syntax version, the output is not valid KPL syntax either.

This is the intent: to succinctly return the results. Not the intent: copy the result verbatim to a policy source file. If I have 50 conditional results it would be a mess. The kernel language was chosen for rendering because SETools predates CIL. SETools did not change to CIL rendering because it is an intermediate language that most don't even see.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same problem exists in the kernel syntax version, the output is not valid KPL syntax either.

This is the intent: to succinctly return the results. Not the intent: copy the result verbatim to a policy source file. If I have 50 conditional results it would be a mess. The kernel language was chosen for rendering because SETools predates CIL. SETools did not change to CIL rendering because it is an intermediate language that most don't even see.

I don't mean to criticize KPL or the choices made. These days I prefer to operate closer to metal with CIL. But since CIL output is a new feature, we could choose to be more verbose, to be more accurate and to be fully compatible with the CIL syntax. If the user wants a compact representation, the default KPL output should be great for that purpose. Perhaps there could be even several output variants, like cil-short for one-line syntax (output booleans as a CIL comment) and cil-verbose for full version.

Copy link
Member

@pebenito pebenito Jun 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since CIL is an intermediate language, I don't feel the need to extensively support it as you describe.

# 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 (NOT) so if there is a single binary
# operator, no parentheses are output
stack = []
prev_op_precedence = 5

for expr_node in self.expression():
if isinstance(expr_node, Boolean):
# append the boolean name
stack.append(str(expr_node))
elif expr_node.unary:
operand = stack.pop()
operator = str(expr_node)
op_precedence = expr_node.precedence

# NOT is the highest precedence, so only need
# parentheses if the operand is a subexpression
if isinstance(operand, list):
subexpr = [operator, "(", operand, ")"]
if self.policy.gen_cil:
# sepol representation is in postfix notation. This code
# converts it to prefix notation used by CIL. Parentheses
# are always needed.
stack = []

for expr_node in self.expression():
if isinstance(expr_node, Boolean):
# append the boolean name
stack.append(str(expr_node))
elif expr_node.unary:
operand = stack.pop()
operator = str(expr_node)

subexpr = ["(", operator, " ", operand, ")"]

stack.append(subexpr)
else:
subexpr = [operator, operand]
operand1 = stack.pop()
operand2 = stack.pop()
operator = str(expr_node)

stack.append(subexpr)
prev_op_precedence = op_precedence
else:
operand1 = stack.pop()
operand2 = stack.pop()
operator = str(expr_node)
op_precedence = expr_node.precedence
subexpr = ["(", operator, " ", operand1, " ", operand2, ")"]

stack.append(subexpr)
return ''.join(flatten_list(stack))

if prev_op_precedence > op_precedence:
# if previous operator is of higher precedence
# no parentheses are needed.
subexpr = [operand1, operator, operand2]
else:
# This code converts sepol 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 (NOT) so if there is a single binary
# operator, no parentheses are output
stack = []
prev_op_precedence = 5

for expr_node in self.expression():
if isinstance(expr_node, Boolean):
# append the boolean name
stack.append(str(expr_node))
elif expr_node.unary:
operand = stack.pop()
operator = str(expr_node)
op_precedence = expr_node.precedence

# NOT is the highest precedence, so only need
# parentheses if the operand is a subexpression
if isinstance(operand, list):
subexpr = [operator, "(", operand, ")"]
else:
subexpr = [operator, operand]

stack.append(subexpr)
prev_op_precedence = op_precedence
else:
subexpr = ["(", operand1, operator, operand2, ")"]
operand1 = stack.pop()
operand2 = stack.pop()
operator = str(expr_node)
op_precedence = expr_node.precedence

if prev_op_precedence > op_precedence:
# if previous operator is of higher precedence
# no parentheses are needed.
subexpr = [operand1, operator, operand2]
else:
subexpr = ["(", operand1, operator, operand2, ")"]

stack.append(subexpr)
prev_op_precedence = op_precedence
stack.append(subexpr)
prev_op_precedence = op_precedence

return ' '.join(flatten_list(stack))
return ' '.join(flatten_list(stack))

def __hash__(self):
return hash(self.key)
Expand Down Expand Up @@ -222,6 +249,7 @@ cdef class ConditionalOperator(PolicyObject):

cdef:
str text
str cil_text
readonly int precedence
# T/F the operator is unary
readonly bint unary
Expand All @@ -231,12 +259,12 @@ cdef class ConditionalOperator(PolicyObject):
readonly object evaluate

_cond_expr_val_to_details = {
sepol.COND_NOT: ("!", 5, lambda x: not x),
sepol.COND_OR: ("||", 1, lambda x, y: x or y),
sepol.COND_AND: ("&&", 3, lambda x, y: x and y),
sepol.COND_XOR: ("^", 2, lambda x, y: x ^ y),
sepol.COND_EQ: ("==", 4, lambda x, y: x == y),
sepol.COND_NEQ: ("!=", 4, lambda x, y: x != y)}
sepol.COND_NOT: ("!", "not", 5, lambda x: not x),
sepol.COND_OR: ("||", "or", 1, lambda x, y: x or y),
sepol.COND_AND: ("&&", "and", 3, lambda x, y: x and y),
sepol.COND_XOR: ("^", "xor", 2, lambda x, y: x ^ y),
sepol.COND_EQ: ("==", "eq", 4, lambda x, y: x == y),
sepol.COND_NEQ: ("!=", "ne", 4, lambda x, y: x != y)}

@staticmethod
cdef inline ConditionalOperator factory(SELinuxPolicy policy, sepol.cond_expr_t *symbol):
Expand All @@ -245,11 +273,14 @@ cdef class ConditionalOperator(PolicyObject):
op.policy = policy
op.key = <uintptr_t>symbol
op.unary = symbol.expr_type == sepol.COND_NOT
op.text, op.precedence, op.evaluate = op._cond_expr_val_to_details[symbol.expr_type]
op.text, op.cil_text, op.precedence, op.evaluate = op._cond_expr_val_to_details[symbol.expr_type]
return op

def __str__(self):
return self.text
if self.policy.gen_cil:
return self.cil_text
else:
return self.text

def statement(self):
raise NoStatement
Expand Down
37 changes: 23 additions & 14 deletions setools/policyrep/mls.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -168,17 +168,20 @@ cdef class BaseMLSLevel(PolicyObject):
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(f"{group[0]}.{group[-1]}")
else:
shortlist.append(str(group[0]))

lvl += ":" + ','.join(shortlist)
if self.policy.gen_cil:
lvl += " (" + " ".join(c.name for c in cats) + ")"
else:
# 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(f"{group[0]}.{group[-1]}")
else:
shortlist.append(str(group[0]))

lvl += ":" + ','.join(shortlist)

return lvl

Expand Down Expand Up @@ -431,10 +434,16 @@ cdef class Range(PolicyObject):
return r

def __str__(self):
if self.high == self.low:
return str(self.low)
if self.policy.gen_cil:
if self.high == self.low:
return f"({self.low})"

return f"({self.low} {self.high})"
else:
if self.high == self.low:
return str(self.low)

return f"{self.low} - {self.high}"
return f"{self.low} - {self.high}"

def __hash__(self):
return hash(str(self))
Expand Down
5 changes: 4 additions & 1 deletion setools/policyrep/mlsrule.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,10 @@ cdef class MLSRule(PolicyRule):
yield self

def statement(self):
return f"{self.ruletype} {self.source} {self.target}:{self.tclass} {self.default};"
if self.policy.gen_cil:
return f"(rangetransition {self.source} {self.target} {self.tclass} {self.default})"
else:
return f"{self.ruletype} {self.source} {self.target}:{self.tclass} {self.default};"
Comment on lines -70 to +73
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The other places in the change should have separate methods for each policy lang, unless they are simple one-liners like this.



#
Expand Down
10 changes: 8 additions & 2 deletions setools/policyrep/rbacrule.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,10 @@ cdef class RoleAllow(PolicyRule):
yield self

def statement(self):
return f"{self.ruletype} {self.source} {self.target};"
if self.policy.gen_cil:
return f"(roleallow {self.source} {self.target})"
else:
return f"{self.ruletype} {self.source} {self.target};"


cdef class RoleTransition(PolicyRule):
Expand Down Expand Up @@ -122,7 +125,10 @@ cdef class RoleTransition(PolicyRule):
yield self

def statement(self):
return f"{self.ruletype} {self.source} {self.target}:{self.tclass} {self.default};"
if self.policy.gen_cil:
return f"(roletransition {self.source} {self.target} {self.tclass} {self.default})"
else:
return f"{self.ruletype} {self.source} {self.target}:{self.tclass} {self.default};"


#
Expand Down
24 changes: 15 additions & 9 deletions setools/policyrep/role.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,21 @@ cdef class Role(PolicySymbol):

def statement(self):
cdef size_t count
types = list(str(t) for t in self._types)
count = len(types)
stmt = f"role {self.name}"
if count == 0:
return f"role {self.name};"
if count == 1:
return f"role {self.name} types {types[0]};"

return f"role {self.name} types {{ {' '.join(sorted(types))} }};"
if self.policy.gen_cil:
stmt = ""
for t in self._types:
stmt += f"(roletype {self} {t})\n"
return stmt
else:
types = list(str(t) for t in self._types)
count = len(types)
stmt = f"role {self.name}"
if count == 0:
return f"role {self.name};"
if count == 1:
return f"role {self.name} types {types[0]};"

return f"role {self.name} types {{ {' '.join(sorted(types))} }};"


#
Expand Down
5 changes: 4 additions & 1 deletion setools/policyrep/selinuxpolicy.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ cdef class SELinuxPolicy:
dict category_alias_map
dict sensitivity_alias_map
object __weakref__
bint gen_cil

# Public attributes:
readonly str path
Expand All @@ -44,7 +45,7 @@ cdef class SELinuxPolicy:
readonly unsigned int version
readonly bint mls

def __cinit__(self, policyfile=None):
def __cinit__(self, policyfile=None, gen_cil=False):
"""
Parameter:
policyfile Path to a policy to open.
Expand All @@ -60,6 +61,8 @@ cdef class SELinuxPolicy:
else:
self._load_running_policy()

self.gen_cil = gen_cil

def __dealloc__(self):
PyMem_Free(self.cat_val_to_struct)
PyMem_Free(self.level_val_to_struct)
Expand Down
Loading
Loading