1 /*
   2  * Copyright (C) 2014 Oracle.
   3  *
   4  * This program is free software; you can redistribute it and/or
   5  * modify it under the terms of the GNU General Public License
   6  * as published by the Free Software Foundation; either version 2
   7  * of the License, or (at your option) any later version.
   8  *
   9  * This program is distributed in the hope that it will be useful,
  10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  * GNU General Public License for more details.
  13  *
  14  * You should have received a copy of the GNU General Public License
  15  * along with this program; if not, see http://www.gnu.org/copyleft/gpl.txt
  16  */
  17 
  18 /*
  19  * This file is sort of like check_dereferences_param.c.  In theory the one
  20  * difference should be that the param is NULL it should still be counted as a
  21  * free.  But for now I don't handle that case.
  22  */
  23 
  24 #include "smatch.h"
  25 #include "smatch_extra.h"
  26 #include "smatch_slist.h"
  27 
  28 static int my_id;
  29 
  30 STATE(freed);
  31 STATE(ignore);
  32 
  33 static struct smatch_state *unmatched_state(struct sm_state *sm)
  34 {
  35         if (sm->state != &freed)
  36                 return &undefined;
  37         if (parent_is_null_var_sym(sm->name, sm->sym))
  38                 return &freed;
  39         return &undefined;
  40 }
  41 
  42 static void set_ignore(struct sm_state *sm, struct expression *mod_expr)
  43 {
  44         set_state(my_id, sm->name, sm->sym, &ignore);
  45 }
  46 
  47 static int counter_was_inced(struct expression *expr)
  48 {
  49         char *name;
  50         struct symbol *sym;
  51         char buf[256];
  52         int ret = 0;
  53 
  54         name = expr_to_var_sym(expr, &sym);
  55         if (!name || !sym)
  56                 goto free;
  57 
  58         snprintf(buf, sizeof(buf), "%s->users.counter", name);
  59         ret = was_inced(buf, sym);
  60 free:
  61         free_string(name);
  62         return ret;
  63 }
  64 
  65 static void match_free(const char *fn, struct expression *expr, void *param)
  66 {
  67         struct expression *arg, *tmp;
  68         int cnt = 0;
  69 
  70         arg = get_argument_from_call_expr(expr->args, PTR_INT(param));
  71         if (!arg)
  72                 return;
  73         while ((tmp = get_assigned_expr(arg))) {
  74                 arg = strip_expr(tmp);
  75                 if (cnt++ > 5)
  76                         break;
  77         }
  78 
  79         if (get_param_num(arg) < 0)
  80                 return;
  81         if (param_was_set(arg))
  82                 return;
  83         if (strcmp(fn, "kfree_skb") == 0 && counter_was_inced(arg))
  84                 return;
  85 
  86         set_state_expr(my_id, arg, &freed);
  87 }
  88 
  89 static void set_param_freed(struct expression *expr, int param, char *key, char *value)
  90 {
  91         struct expression *arg;
  92         char *name;
  93         struct symbol *sym;
  94 
  95         while (expr->type == EXPR_ASSIGNMENT)
  96                 expr = strip_expr(expr->right);
  97         if (expr->type != EXPR_CALL)
  98                 return;
  99 
 100         arg = get_argument_from_call_expr(expr->args, param);
 101         if (!arg)
 102                 return;
 103         name = get_variable_from_key(arg, key, &sym);
 104         if (!name || !sym)
 105                 goto free;
 106         if (get_param_num_from_sym(sym) < 0)
 107                 goto free;
 108 
 109         if (param_was_set_var_sym(name, sym))
 110                 goto free;
 111 
 112         set_state(my_id, name, sym, &freed);
 113 free:
 114         free_string(name);
 115 }
 116 
 117 static void param_freed_info(int return_id, char *return_ranges, struct expression *expr)
 118 {
 119         struct sm_state *sm;
 120         int param;
 121         const char *param_name;
 122 
 123         if (on_atomic_dec_path())
 124                 return;
 125 
 126         FOR_EACH_MY_SM(my_id, __get_cur_stree(), sm) {
 127                 if (sm->state != &freed)
 128                         continue;
 129 
 130                 param = get_param_num_from_sym(sm->sym);
 131                 if (param < 0)
 132                         continue;
 133 
 134                 param_name = get_param_name(sm);
 135                 if (!param_name)
 136                         continue;
 137 
 138                 sql_insert_return_states(return_id, return_ranges, PARAM_FREED,
 139                                          param, param_name, "");
 140         } END_FOR_EACH_SM(sm);
 141 }
 142 
 143 void check_frees_param_strict(int id)
 144 {
 145         my_id = id;
 146 
 147         if (option_project != PROJ_KERNEL)
 148                 return;
 149 
 150         add_function_hook("kfree", &match_free, INT_PTR(0));
 151         add_function_hook("kmem_cache_free", &match_free, INT_PTR(1));
 152         add_function_hook("kfree_skb", &match_free, INT_PTR(0));
 153         add_function_hook("kfree_skbmem", &match_free, INT_PTR(0));
 154         add_function_hook("dma_pool_free", &match_free, INT_PTR(1));
 155         add_function_hook("spi_unregister_controller", &match_free, INT_PTR(0));
 156 
 157         select_return_states_hook(PARAM_FREED, &set_param_freed);
 158         add_modification_hook(my_id, &set_ignore);
 159         add_split_return_callback(&param_freed_info);
 160 
 161         add_unmatched_state_hook(my_id, &unmatched_state);
 162 }