2
0
mirror of https://gitlab.com/apparmor/apparmor synced 2025-09-03 07:45:50 +00:00

parser: expand conditionals to allow comparisons

The apparmor parser supports if comparisons of boolean variables and
the definition status of set variables.

This commit expands the currently supported set to include comparisons
such as 'in', '>', '>=', '<', '<=', '==', and '!=' between
variables and/or text.

The comparison is done in lexicographical order, and since that can
cause issues comparing numbers, comparison between sets and numbers is
not allowed and the profile will fail to compile. Please refer to
apparmor.d.pod for example and details.

This commit also adds a file that generates test cases in the
parser. It is generated automatically with make check, but you can
generate them by running

make -C tst gen_conditionals

The generated tests will be under
tst/simple_tests/generated_conditional/

Signed-off-by: Georgia Garcia <georgia.garcia@canonical.com>
This commit is contained in:
Georgia Garcia
2025-06-20 18:43:31 -03:00
parent bf82f41d90
commit 8d0c248fe4
8 changed files with 381 additions and 36 deletions

View File

@@ -16,6 +16,8 @@
* Ltd.
*/
#include <algorithm>
#include "cond_expr.h"
#include "parser.h"
#include "symtab.h"
@@ -25,17 +27,16 @@ cond_expr::cond_expr(bool result):
{
}
cond_expr::cond_expr(const char *var, bool defined)
cond_expr::cond_expr(const char *var, cond_op op)
{
variable *ref;
if (!defined) {
if (op == BOOLEAN_OP) {
ref = symtab::get_boolean_var(var);
if (!ref) {
/* FIXME check for set var */
yyerror(_("Unset boolean variable %s used in if-expression"), var);
}
result = ref->boolean;
} else {
} else if (op == DEFINED_OP) {
ref = symtab::get_set_var(var);
if (!ref) {
result = false;
@@ -43,5 +44,106 @@ cond_expr::cond_expr(const char *var, bool defined)
PDEBUG("Matched: defined set expr %s value %s\n", var, ref->expanded.begin()->c_str());
result = true;
}
} else
PERROR("Invalid operation for if-expression");
}
/* variables passed in conditionals can be variables or values.
if the string passed has the formatting of a variable (@{}), then
we should look for it in the symtab. if it's present in the symtab,
expand its values and return the expanded set. if it's not present
in the symtab, we should error out. if the string passed does not
have the formatting of a variable, we should treat it as if it was
a value. add it to a set and return it so comparisons can be made.
*/
std::set<std::string> cond_expr::get_set(const char *var)
{
char *var_name = variable::process_var(var);
if (!var_name) {
/* not a variable */
return {var};
}
variable *ref = symtab::lookup_existing_symbol(var_name);
free(var_name);
if (!ref) {
yyerror(_("Error retrieving variable %s"), var);
}
if (ref->expand_variable() != 0) {
/* expand_variable prints error messages already, so
* exit quietly here */
exit(1);
}
return ref->expanded;
}
template <typename T>
void cond_expr::compare(cond_op op, T lhs, T rhs)
{
switch (op) {
case GT_OP:
result = lhs > rhs;
break;
case GE_OP:
result = lhs >= rhs;
break;
case LT_OP:
result = lhs < rhs;
break;
case LE_OP:
result = lhs <= rhs;
break;
default:
PDEBUG("Invalid op\n");
}
}
bool nullstr(char *p)
{
return p && !(*p);
}
long str_set_to_long(std::set<std::string> &src, char **endptr)
{
long converted_src = 0;
errno = 0;
if (src.size() == 1 && !src.begin()->empty())
converted_src = strtol(src.begin()->c_str(), endptr, 0);
if (errno == ERANGE)
yyerror(_("Value out of valid range\n"));
return converted_src;
}
cond_expr::cond_expr(const char *lhv, cond_op op, const char *rhv)
{
std::set<std::string> lhs = get_set(lhv);
std::set<std::string> rhs = get_set(rhv);
char *p_lhs = NULL, *p_rhs = NULL;
long converted_lhs = 0, converted_rhs = 0;
if (op == IN_OP) {
/* if lhs is a subset of rhs */
result = std::includes(rhs.begin(), rhs.end(),
lhs.begin(), lhs.end());
return;
} else if (op == EQ_OP) {
result = lhs == rhs;
return;
} else if (op == NE_OP) {
result = lhs != rhs;
return;
}
converted_lhs = str_set_to_long(lhs, &p_lhs);
converted_rhs = str_set_to_long(rhs, &p_rhs);
if (!nullstr(p_lhs) && !nullstr(p_rhs)) {
/* sets */
compare(op, lhs, rhs);
} else if (nullstr(p_lhs) && nullstr(p_rhs)) {
/* numbers */
compare(op, converted_lhs, converted_rhs);
} else {
yyerror(_("Can only compare numbers with numbers\n"));
}
}