#include "smatch.h"
#include "smatch_extra.h"
static int my_id;
struct allocator {
const char *func;
int param;
int param2;
};
static struct allocator generic_allocator_table[] = {
{"malloc", 0},
{"memdup", 1},
{"realloc", 1},
};
static struct allocator kernel_allocator_table[] = {
{"kmalloc", 0},
{"kzalloc", 0},
{"vmalloc", 0},
{"__vmalloc", 0},
{"vzalloc", 0},
{"sock_kmalloc", 1},
{"kmemdup", 1},
{"kmemdup_user", 1},
{"dma_alloc_attrs", 1},
{"pci_alloc_consistent", 1},
{"pci_alloc_coherent", 1},
{"devm_kmalloc", 1},
{"devm_kzalloc", 1},
{"krealloc", 1},
};
static struct allocator calloc_table[] = {
{"calloc", 0, 1},
{"kcalloc", 0, 1},
{"kmalloc_array", 0, 1},
{"devm_kcalloc", 1, 2},
};
static int bytes_per_element(struct expression *expr)
{
struct symbol *type;
type = get_type(expr);
if (!type)
return 0;
if (type->type != SYM_PTR && type->type != SYM_ARRAY)
return 0;
type = get_base_type(type);
return type_bytes(type);
}
static void save_constraint_required(struct expression *pointer, int op, struct expression *constraint)
{
char *data, *limit;
data = get_constraint_str(pointer);
if (!data)
return;
limit = get_constraint_str(constraint);
if (!limit) {
if (op == '<')
set_state_expr(my_id, constraint, alloc_state_expr(pointer));
goto free_data;
}
sql_save_constraint_required(data, op, limit);
free_string(limit);
free_data:
free_string(data);
}
static int handle_zero_size_arrays(struct expression *pointer, struct expression *size)
{
struct expression *left, *right;
struct symbol *type, *array, *array_type;
sval_t struct_size;
char *limit;
char data[128];
if (size->type != EXPR_BINOP || size->op != '+')
return 0;
type = get_type(pointer);
if (!type || type->type != SYM_PTR)
return 0;
type = get_real_base_type(type);
if (!type || !type->ident || type->type != SYM_STRUCT)
return 0;
if (!last_member_is_resizable(type))
return 0;
array = last_ptr_list((struct ptr_list *)type->symbol_list);
if (!array || !array->ident)
return 0;
array_type = get_real_base_type(array);
if (!array_type || array_type->type != SYM_ARRAY)
return 0;
array_type = get_real_base_type(array_type);
left = strip_expr(size->left);
right = strip_expr(size->right);
if (!get_implied_value(left, &struct_size))
return 0;
if (struct_size.value != type_bytes(type))
return 0;
if (right->type == EXPR_BINOP && right->op == '*') {
struct expression *mult_left, *mult_right;
sval_t sval;
mult_left = strip_expr(right->left);
mult_right = strip_expr(right->right);
if (get_implied_value(mult_left, &sval) &&
sval.value == type_bytes(array_type))
size = mult_right;
else if (get_implied_value(mult_right, &sval) &&
sval.value == type_bytes(array_type))
size = mult_left;
else
return 0;
}
snprintf(data, sizeof(data), "(struct %s)->%s", type->ident->name, array->ident->name);
limit = get_constraint_str(size);
if (!limit) {
set_state_expr(my_id, size, alloc_state_expr(
member_expression(deref_expression(pointer), '*', array->ident)));
return 1;
}
sql_save_constraint_required(data, '<', limit);
free_string(limit);
return 1;
}
static void match_alloc_helper(struct expression *pointer, struct expression *size, int recurse)
{
struct expression *size_orig, *tmp;
sval_t sval;
int cnt = 0;
pointer = strip_expr(pointer);
size = strip_expr(size);
if (!size || !pointer)
return;
size_orig = size;
if (recurse) {
while ((tmp = get_assigned_expr(size))) {
size = strip_expr(tmp);
if (cnt++ > 5)
break;
}
if (size != size_orig) {
match_alloc_helper(pointer, size, 0);
size = size_orig;
}
}
if (handle_zero_size_arrays(pointer, size))
return;
if (size->type == EXPR_BINOP && size->op == '*') {
struct expression *mult_left, *mult_right;
mult_left = strip_expr(size->left);
mult_right = strip_expr(size->right);
if (get_implied_value(mult_left, &sval) &&
sval.value == bytes_per_element(pointer))
size = mult_right;
else if (get_implied_value(mult_right, &sval) &&
sval.value == bytes_per_element(pointer))
size = mult_left;
else
return;
}
if (size->type == EXPR_BINOP && size->op == '+' &&
get_implied_value(size->right, &sval) &&
sval.value == 1)
save_constraint_required(pointer, SPECIAL_LTE, size->left);
else
save_constraint_required(pointer, '<', size);
}
static void match_alloc(const char *fn, struct expression *expr, void *_size_arg)
{
int size_arg = PTR_INT(_size_arg);
struct expression *call, *arg;
call = strip_expr(expr->right);
arg = get_argument_from_call_expr(call->args, size_arg);
match_alloc_helper(expr->left, arg, 1);
}
static void match_calloc(const char *fn, struct expression *expr, void *_start_arg)
{
struct expression *pointer, *call, *size;
struct expression *count = NULL;
int start_arg = PTR_INT(_start_arg);
sval_t sval;
pointer = strip_expr(expr->left);
call = strip_expr(expr->right);
size = get_argument_from_call_expr(call->args, start_arg);
if (get_implied_value(size, &sval) &&
sval.value == bytes_per_element(pointer))
count = get_argument_from_call_expr(call->args, start_arg + 1);
else {
size = get_argument_from_call_expr(call->args, start_arg + 1);
if (get_implied_value(size, &sval) &&
sval.value == bytes_per_element(pointer))
count = get_argument_from_call_expr(call->args, start_arg);
}
if (!count)
return;
save_constraint_required(pointer, '<', count);
}
static void add_allocation_function(const char *func, void *call_back, int param)
{
add_function_assign_hook(func, call_back, INT_PTR(param));
}
static void match_assign_size(struct expression *expr)
{
struct smatch_state *state;
char *data, *limit;
state = get_state_expr(my_id, expr->right);
if (!state || !state->data)
return;
data = get_constraint_str(state->data);
if (!data)
return;
limit = get_constraint_str(expr->left);
if (!limit)
goto free_data;
sql_save_constraint_required(data, '<', limit);
free_string(limit);
free_data:
free_string(data);
}
static void match_assign_has_buf_comparison(struct expression *expr)
{
struct expression *size;
int limit_type;
if (expr->op != '=')
return;
if (expr->right->type == EXPR_CALL)
return;
size = get_size_variable(expr->right, &limit_type);
if (!size)
return;
if (limit_type != ELEM_COUNT)
return;
match_alloc_helper(expr->left, size, 1);
}
static void match_assign_data(struct expression *expr)
{
struct expression *right, *arg, *tmp;
int i;
int size_arg;
int size_arg2 = -1;
if (expr->op != '=')
return;
tmp = get_assigned_expr(expr->right);
if (!tmp)
return;
right = strip_expr(tmp);
if (right->type != EXPR_CALL)
return;
if (right->fn->type != EXPR_SYMBOL ||
!right->fn->symbol ||
!right->fn->symbol->ident)
return;
for (i = 0; i < ARRAY_SIZE(generic_allocator_table); i++) {
if (strcmp(right->fn->symbol->ident->name,
generic_allocator_table[i].func) == 0) {
size_arg = generic_allocator_table[i].param;
goto found;
}
}
if (option_project != PROJ_KERNEL)
return;
for (i = 0; i < ARRAY_SIZE(kernel_allocator_table); i++) {
if (strcmp(right->fn->symbol->ident->name,
kernel_allocator_table[i].func) == 0) {
size_arg = kernel_allocator_table[i].param;
goto found;
}
}
for (i = 0; i < ARRAY_SIZE(calloc_table); i++) {
if (strcmp(right->fn->symbol->ident->name,
calloc_table[i].func) == 0) {
size_arg = calloc_table[i].param;
size_arg2 = calloc_table[i].param2;
goto found;
}
}
return;
found:
arg = get_argument_from_call_expr(right->args, size_arg);
match_alloc_helper(expr->left, arg, 1);
if (size_arg2 == -1)
return;
arg = get_argument_from_call_expr(right->args, size_arg2);
match_alloc_helper(expr->left, arg, 1);
}
static void match_assign_ARRAY_SIZE(struct expression *expr)
{
struct expression *array;
char *data, *limit;
const char *macro;
macro = get_macro_name(expr->right->pos);
if (!macro || strcmp(macro, "ARRAY_SIZE") != 0)
return;
array = strip_expr(expr->right);
if (array->type != EXPR_BINOP || array->op != '+')
return;
array = strip_expr(array->left);
if (array->type != EXPR_BINOP || array->op != '/')
return;
array = strip_expr(array->left);
if (array->type != EXPR_SIZEOF)
return;
array = strip_expr(array->cast_expression);
if (array->type != EXPR_PREOP || array->op != '*')
return;
array = strip_expr(array->unop);
data = get_constraint_str(array);
limit = get_constraint_str(expr->left);
if (!data || !limit)
goto free;
sql_save_constraint_required(data, '<', limit);
free:
free_string(data);
free_string(limit);
}
static void match_assign_buf_comparison(struct expression *expr)
{
struct expression *pointer;
if (expr->op != '=')
return;
pointer = get_array_variable(expr->right);
if (!pointer)
return;
match_alloc_helper(pointer, expr->right, 1);
}
static int constraint_found(void *_found, int argc, char **argv, char **azColName)
{
int *found = _found;
*found = 1;
return 0;
}
static int has_constraint(struct expression *expr, const char *constraint)
{
int found = 0;
if (get_state_expr(my_id, expr))
return 1;
run_sql(constraint_found, &found,
"select data from constraints_required where bound = '%q' limit 1",
escape_newlines(constraint));
return found;
}
static void match_assign_constraint(struct expression *expr)
{
struct symbol *type;
char *left, *right;
if (expr->op != '=')
return;
type = get_type(expr->left);
if (!type || type->type != SYM_BASETYPE)
return;
left = get_constraint_str(expr->left);
if (!left)
return;
right = get_constraint_str(expr->right);
if (!right)
goto free;
if (!has_constraint(expr->right, right))
return;
sql_copy_constraint_required(left, right);
free:
free_string(right);
free_string(left);
}
void register_constraints_required(int id)
{
my_id = id;
set_dynamic_states(my_id);
add_hook(&match_assign_size, ASSIGNMENT_HOOK);
add_hook(&match_assign_data, ASSIGNMENT_HOOK);
add_hook(&match_assign_has_buf_comparison, ASSIGNMENT_HOOK);
add_hook(&match_assign_ARRAY_SIZE, ASSIGNMENT_HOOK);
add_hook(&match_assign_ARRAY_SIZE, GLOBAL_ASSIGNMENT_HOOK);
add_hook(&match_assign_buf_comparison, ASSIGNMENT_HOOK);
add_hook(&match_assign_constraint, ASSIGNMENT_HOOK);
add_allocation_function("malloc", &match_alloc, 0);
add_allocation_function("memdup", &match_alloc, 1);
add_allocation_function("realloc", &match_alloc, 1);
add_allocation_function("realloc", &match_calloc, 0);
if (option_project == PROJ_KERNEL) {
add_allocation_function("kmalloc", &match_alloc, 0);
add_allocation_function("kzalloc", &match_alloc, 0);
add_allocation_function("vmalloc", &match_alloc, 0);
add_allocation_function("__vmalloc", &match_alloc, 0);
add_allocation_function("vzalloc", &match_alloc, 0);
add_allocation_function("sock_kmalloc", &match_alloc, 1);
add_allocation_function("kmemdup", &match_alloc, 1);
add_allocation_function("kmemdup_user", &match_alloc, 1);
add_allocation_function("dma_alloc_attrs", &match_alloc, 1);
add_allocation_function("pci_alloc_consistent", &match_alloc, 1);
add_allocation_function("pci_alloc_coherent", &match_alloc, 1);
add_allocation_function("devm_kmalloc", &match_alloc, 1);
add_allocation_function("devm_kzalloc", &match_alloc, 1);
add_allocation_function("kcalloc", &match_calloc, 0);
add_allocation_function("kmalloc_array", &match_calloc, 0);
add_allocation_function("devm_kcalloc", &match_calloc, 1);
add_allocation_function("krealloc", &match_alloc, 1);
}
}