#include <stdlib.h>
#include "parse.h"
#include "smatch.h"
#include "smatch_slist.h"
#include "smatch_extra.h"
static int loop_id;
STATE(loop_end);
static int definitely_just_used_as_limiter(struct expression *array, struct expression *offset)
{
sval_t sval;
struct expression *tmp;
if (!get_implied_value(offset, &sval))
return 0;
if (get_array_size(array) != sval.value)
return 0;
tmp = array;
while ((tmp = expr_get_parent_expr(tmp))) {
if (tmp->type == EXPR_PREOP && tmp->op == '&')
return 1;
}
return 0;
}
static int fake_get_hard_max(struct expression *expr, sval_t *sval)
{
struct range_list *implied_rl;
if (!get_hard_max(expr, sval))
return 0;
if (get_implied_rl(expr, &implied_rl) &&
sval_cmp(rl_max(implied_rl), *sval) < 0)
*sval = rl_max(implied_rl);
return 1;
}
static int get_the_max(struct expression *expr, sval_t *sval)
{
struct range_list *rl;
if (get_hard_max(expr, sval)) {
struct range_list *implied_rl;
if (get_implied_rl(expr, &implied_rl) &&
sval_cmp(rl_max(implied_rl), *sval) < 0)
*sval = rl_max(implied_rl);
return 1;
}
if (!option_spammy)
return 0;
if (!get_user_rl(expr, &rl))
return 0;
if (rl_max(rl).uvalue > sval_type_max(rl_type(rl)).uvalue - 4 &&
is_capped(expr))
return 0;
*sval = rl_max(rl);
return 1;
}
static int common_false_positives(struct expression *array, sval_t max)
{
char *name;
int ret;
name = expr_to_str(array);
if (name &&
(strcmp(name, "__s1") == 0 || strcmp(name, "__s2") == 0)) {
ret = 1;
goto free;
}
if (array) {
char *macro;
if (array->type == EXPR_STRING &&
max.value == array->string->length) {
ret = 1;
goto free;
}
macro = get_macro_name(array->pos);
if (macro && max.uvalue < 4 &&
(strcmp(macro, "strcmp") == 0 ||
strcmp(macro, "strncmp") == 0 ||
strcmp(macro, "streq") == 0 ||
strcmp(macro, "strneq") == 0 ||
strcmp(macro, "strsep") == 0)) {
ret = 1;
goto free;
}
}
if (option_project == PROJ_KERNEL && name &&
strcmp(name, "__per_cpu_offset") == 0) {
ret = 1;
goto free;
}
ret = 0;
free:
free_string(name);
return ret;
}
static int is_subtract(struct expression *expr)
{
struct expression *tmp;
int cnt = 0;
expr = strip_expr(expr);
while ((tmp = get_assigned_expr(expr))) {
expr = strip_expr(tmp);
if (++cnt > 5)
break;
}
if (expr->type == EXPR_BINOP && expr->op == '-')
return 1;
return 0;
}
static int constraint_met(struct expression *array_expr, struct expression *offset)
{
char *data_str, *required, *unmet;
int ret = 0;
data_str = get_constraint_str(array_expr);
if (!data_str)
return 0;
required = get_required_constraint(data_str);
if (!required)
goto free_data_str;
unmet = unmet_constraint(array_expr, offset);
if (!unmet)
ret = 1;
free_string(unmet);
free_string(required);
free_data_str:
free_string(data_str);
return ret;
}
static int should_warn(struct expression *expr)
{
struct expression *array_expr;
struct range_list *abs_rl;
sval_t hard_max = { .type = &int_ctype, };
sval_t fuzzy_max = { .type = &int_ctype, };
int array_size;
struct expression *offset;
sval_t max;
expr = strip_expr(expr);
if (!is_array(expr))
return 0;
if (is_impossible_path())
return 0;
array_expr = get_array_base(expr);
array_size = get_array_size(array_expr);
if (!array_size || array_size == 1)
return 0;
offset = get_array_offset(expr);
get_absolute_rl(offset, &abs_rl);
fake_get_hard_max(offset, &hard_max);
get_fuzzy_max(offset, &fuzzy_max);
if (!get_the_max(offset, &max))
return 0;
if (array_size > max.value)
return 0;
if (constraint_met(array_expr, offset))
return 0;
if (array_size > rl_max(abs_rl).uvalue)
return 0;
if (definitely_just_used_as_limiter(array_expr, offset))
return 0;
array_expr = strip_expr(array_expr);
if (common_false_positives(array_expr, max))
return 0;
if (impossibly_high_comparison(offset))
return 0;
return 1;
}
static int is_because_of_no_break(struct expression *offset)
{
if (get_state_expr(loop_id, offset) == &loop_end)
return 1;
return 0;
}
static void array_check(struct expression *expr)
{
struct expression *array_expr;
struct range_list *abs_rl;
struct range_list *user_rl = NULL;
sval_t hard_max = { .type = &int_ctype, };
sval_t fuzzy_max = { .type = &int_ctype, };
int array_size;
struct expression *array_size_value, *comparison;
struct expression *offset;
sval_t max;
char *name;
int no_break = 0;
if (!should_warn(expr))
return;
expr = strip_expr(expr);
array_expr = get_array_base(expr);
array_size = get_array_size(array_expr);
offset = get_array_offset(expr);
array_size_value = value_expr(array_size);
comparison = compare_expression(offset, SPECIAL_GTE, array_size_value);
if (assume(comparison)) {
if (!should_warn(expr)) {
end_assume();
return;
}
no_break = is_because_of_no_break(offset);
end_assume();
}
get_absolute_rl(offset, &abs_rl);
get_user_rl(offset, &user_rl);
fake_get_hard_max(offset, &hard_max);
get_fuzzy_max(offset, &fuzzy_max);
array_expr = strip_expr(array_expr);
name = expr_to_str(array_expr);
if (user_rl)
max = rl_max(user_rl);
else
max = rl_max(abs_rl);
if (!option_spammy && is_subtract(offset))
return;
if (no_break) {
sm_error("buffer overflow '%s' %d <= %s (assuming for loop doesn't break)",
name, array_size, sval_to_str(max));
} else if (user_rl) {
sm_error("buffer overflow '%s' %d <= %s user_rl='%s'%s",
name, array_size, sval_to_str(max), show_rl(user_rl),
is_subtract(offset) ? " subtract" : "");
} else {
sm_error("buffer overflow '%s' %d <= %s%s",
name, array_size, sval_to_str(max),
is_subtract(offset) ? " subtract" : "");
}
free_string(name);
}
void check_index_overflow(int id)
{
add_hook(&array_check, OP_HOOK);
}
static void match_condition(struct expression *expr)
{
struct statement *stmt;
if (expr->type != EXPR_COMPARE)
return;
if (expr->op != '<' && expr->op != SPECIAL_UNSIGNED_LT)
return;
stmt = expr_get_parent_stmt(expr);
if (!stmt || stmt->type != STMT_ITERATOR)
return;
set_true_false_states_expr(loop_id, expr->left, NULL, &loop_end);
}
static void set_undefined(struct sm_state *sm, struct expression *mod_expr)
{
if (sm->state == &loop_end)
set_state(loop_id, sm->name, sm->sym, &undefined);
}
void check_index_overflow_loop_marker(int id)
{
loop_id = id;
add_hook(&match_condition, CONDITION_HOOK);
add_modification_hook(loop_id, &set_undefined);
}