#include "smatch.h"
#include "smatch_extra.h"
#include "smatch_function_hashtable.h"
static bool expr_has_memory_addr(struct expression *expr);
static DEFINE_HASHTABLE_SEARCH(search_symbol, char, char);
static DEFINE_HASHTABLE_INSERT(insert_symbol, char, char);
static struct hashtable *symbols;
static void match_assign(struct expression *expr)
{
char *left_name;
struct symbol *left_sym;
left_name = expr_to_var_sym(expr->left, &left_sym);
if (!left_name || !left_sym)
return;
if (expr_has_memory_addr(expr->right))
insert_symbol(symbols, left_name, left_name);
}
static void match_endfunc(struct symbol *sym)
{
destroy_function_hashtable(symbols);
symbols = create_function_hashtable(4000);
}
static bool expr_has_untagged_symbol(struct expression *expr)
{
char *name;
struct symbol *sym;
if (expr->type != EXPR_SYMBOL)
return false;
name = expr_to_var_sym(expr, &sym);
if (!name || !sym)
return false;
if (search_symbol(symbols, name))
return true;
return false;
}
static bool expr_has_untagged_member(struct expression *expr)
{
if (expr->type != EXPR_DEREF)
return false;
if (!strcmp(expr->member->name, "vm_start") ||
!strcmp(expr->member->name, "vm_end") ||
!strcmp(expr->member->name, "addr_limit"))
return true;
return false;
}
static bool expr_has_macro_with_name(struct expression *expr, const char *macro_name)
{
char *name;
name = get_macro_name(expr->pos);
return (name && !strcmp(name, macro_name));
}
static bool expr_has_untagged_macro(struct expression *expr)
{
if (expr_has_macro_with_name(expr, "PAGE_SIZE") ||
expr_has_macro_with_name(expr, "PAGE_MASK") ||
expr_has_macro_with_name(expr, "TASK_SIZE"))
return true;
if (expr_has_macro_with_name(expr, "offset_in_page"))
return true;
return false;
}
static bool expr_has_memory_addr(struct expression *expr)
{
if (expr->type == EXPR_PREOP || expr->type == EXPR_POSTOP)
expr = strip_expr(expr->unop);
if (expr_has_untagged_member(expr))
return true;
if (expr_has_untagged_macro(expr))
return true;
if (expr_has_untagged_symbol(expr))
return true;
return false;
}
int rl_is_larger_or_equal(struct range_list *rl, sval_t sval)
{
struct data_range *tmp;
FOR_EACH_PTR(rl, tmp) {
if (sval_cmp(tmp->max, sval) >= 0)
return 1;
} END_FOR_EACH_PTR(tmp);
return 0;
}
int rl_range_has_min_value(struct range_list *rl, sval_t sval)
{
struct data_range *tmp;
FOR_EACH_PTR(rl, tmp) {
if (!sval_cmp(tmp->min, sval)) {
return 1;
}
} END_FOR_EACH_PTR(tmp);
return 0;
}
static bool rl_is_tagged(struct range_list *rl)
{
sval_t invalid;
sval_t invalid_kernel;
invalid.type = &ullong_ctype;
invalid.value = 1ULL << 56;
invalid_kernel.type = &ullong_ctype;
invalid_kernel.value = 0xff8ULL << 52;
if (!rl_is_larger_or_equal(rl, invalid))
return false;
if (rl_range_has_min_value(rl, invalid_kernel))
return false;
return true;
}
static void match_condition(struct expression *expr)
{
struct range_list *rl = NULL;
struct expression *val = NULL;
struct symbol *type;
char *var_name;
if (expr->type != EXPR_COMPARE &&
expr->type != EXPR_BINOP)
return;
if (expr_has_memory_addr(expr->left))
val = expr->right;
if (expr_has_memory_addr(expr->right))
val = expr->left;
if (!val)
return;
type = get_type(val);
if (!type || type_bits(type) != 64)
return;
if (!get_user_rl(val, &rl))
return;
if (!rl_is_tagged(rl))
return;
var_name = expr_to_var(val);
if (var_name)
sm_warning("comparison of a potentially tagged address (%s, %d, %s)", get_function(), get_param_num(val), var_name);
}
void check_arm64_tagged(int id)
{
char *arch;
if (option_project != PROJ_KERNEL)
return;
arch = getenv("ARCH");
if (!arch || strcmp(arch, "arm64"))
return;
symbols = create_function_hashtable(4000);
add_hook(&match_assign, ASSIGNMENT_HOOK);
add_hook(&match_condition, CONDITION_HOOK);
add_hook(&match_endfunc, END_FUNC_HOOK);
}