root/usr/src/tools/smatch/src/check_index_overflow.c
/*
 * Copyright (C) 2010 Dan Carpenter.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see http://www.gnu.org/copyleft/gpl.txt
 */

#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;

        /*
         * The problem is that hard_max doesn't care about minimums
         * properly.  So if you give it thing like:
         *      err = (-10)-(-1)
         *      __smatch_hard_max(-err);
         *
         * Then it returns s32max instead of 10.
         */

        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;

                /*
                 * The problem is that hard_max doesn't care about minimums
                 * properly.  So if you give it thing like:
                 *      err = (-10)-(-1)
                 *      __smatch_hard_max(-err);
                 *
                 * Then it returns s32max instead of 10.
                 */

                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;

        /* Fixme:  use fuzzy max */

        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);

        /* Smatch can't figure out glibc's strcmp __strcmp_cg()
         * so it prints an error every time you compare to a string
         * literal array with 4 or less chars.
         */
        if (name &&
            (strcmp(name, "__s1") == 0 || strcmp(name, "__s2") == 0)) {
                ret = 1;
                goto free;
        }

        /* Ugh... People are saying that Smatch still barfs on glibc strcmp()
         * functions.
         */
        if (array) {
                char *macro;

                /* why is this again??? */
                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;
                }
        }

        /*
         * passing WORK_CPU_UNBOUND is idiomatic but Smatch doesn't understand
         * how it's used so it causes a bunch of false positives.
         */
        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);

        /*
         * Perhaps if the offset is out of bounds that means a constraint
         * applies or maybe it means we are on an impossible path.  So test
         * again based on that assumption.
         *
         */
        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);
}