1 /*
   2  * Copyright (C) 2018 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 #include "smatch.h"
  19 #include "smatch_slist.h"
  20 
  21 static int my_id;
  22 static int param_set_id;
  23 
  24 STATE(terminated);
  25 STATE(unterminated);
  26 STATE(set);
  27 
  28 static void set_terminated_var_sym(const char *name, struct symbol *sym, struct smatch_state *state)
  29 {
  30         if (get_param_num_from_sym(sym) >= 0)
  31                 set_state(param_set_id, name, sym, &set);
  32         set_state(my_id, name, sym, state);
  33 }
  34 
  35 static void set_terminated(struct expression *expr, struct smatch_state *state)
  36 {
  37         struct symbol *sym;
  38         char *name;
  39 
  40         name = expr_to_var_sym(expr, &sym);
  41         if (!name || !sym)
  42                 return;
  43         set_terminated_var_sym(name, sym, state);
  44         free_string(name);
  45 }
  46 
  47 static void match_nul_assign(struct expression *expr)
  48 {
  49         struct expression *array;
  50         struct symbol *type;
  51         sval_t sval;
  52 
  53         if (expr->op != '=')
  54                 return;
  55 
  56         if (!get_value(expr->right, &sval) || sval.value != 0)
  57                 return;
  58 
  59         array = get_array_base(expr->left);
  60         if (!array)
  61                 return;
  62 
  63         type = get_type(array);
  64         if (!type)
  65                 return;
  66         type = get_real_base_type(type);
  67         if (type != &char_ctype)
  68                 return;
  69         set_terminated(array, &terminated);
  70 }
  71 
  72 static struct smatch_state *get_terminated_state_var_sym(const char *name, struct symbol *sym)
  73 {
  74         struct sm_state *sm, *tmp;
  75 
  76         sm = get_sm_state(my_id, name, sym);
  77         if (!sm)
  78                 return NULL;
  79         if (sm->state == &terminated || sm->state == &unterminated)
  80                 return sm->state;
  81 
  82         FOR_EACH_PTR(sm->possible, tmp) {
  83                 if (tmp->state == &unterminated)
  84                         return &unterminated;
  85         } END_FOR_EACH_PTR(tmp);
  86 
  87         return NULL;
  88 }
  89 
  90 static struct smatch_state *get_terminated_state(struct expression *expr)
  91 {
  92         struct sm_state *sm, *tmp;
  93 
  94         if (!expr)
  95                 return NULL;
  96         if (expr->type == EXPR_STRING)
  97                 return &terminated;
  98         sm = get_sm_state_expr(my_id, expr);
  99         if (!sm)
 100                 return NULL;
 101         if (sm->state == &terminated || sm->state == &unterminated)
 102                 return sm->state;
 103 
 104         FOR_EACH_PTR(sm->possible, tmp) {
 105                 if (tmp->state == &unterminated)
 106                         return &unterminated;
 107         } END_FOR_EACH_PTR(tmp);
 108 
 109         return NULL;
 110 }
 111 
 112 static void match_string_assign(struct expression *expr)
 113 {
 114         struct smatch_state *state;
 115 
 116         if (expr->op != '=')
 117                 return;
 118         state = get_terminated_state(expr->right);
 119         if (!state)
 120                 return;
 121         set_terminated(expr->left, state);
 122 }
 123 
 124 static int sm_to_term(struct sm_state *sm)
 125 {
 126         struct sm_state *tmp;
 127 
 128         if (!sm)
 129                 return -1;
 130         if (sm->state == &terminated)
 131                 return 1;
 132 
 133         FOR_EACH_PTR(sm->possible, tmp) {
 134                 if (tmp->state == &unterminated)
 135                         return 0;
 136         } END_FOR_EACH_PTR(tmp);
 137 
 138         return -1;
 139 }
 140 
 141 static void struct_member_callback(struct expression *call, int param, char *printed_name, struct sm_state *sm)
 142 {
 143         int term;
 144 
 145         term = sm_to_term(sm);
 146         if (term < 0)
 147                 return;
 148 
 149         sql_insert_caller_info(call, TERMINATED, param, printed_name, term ? "1" : "0");
 150 }
 151 
 152 static void match_call_info(struct expression *expr)
 153 {
 154         struct smatch_state *state;
 155         struct expression *arg;
 156         int i;
 157 
 158         i = -1;
 159         FOR_EACH_PTR(expr->args, arg) {
 160                 i++;
 161 
 162                 state = get_terminated_state(arg);
 163                 if (!state)
 164                         continue;
 165                 sql_insert_caller_info(expr, TERMINATED, i, "$",
 166                                        (state == &terminated) ? "1" : "0");
 167         } END_FOR_EACH_PTR(arg);
 168 }
 169 
 170 static void caller_info_terminated(const char *name, struct symbol *sym, char *key, char *value)
 171 {
 172         char fullname[256];
 173 
 174         if (strcmp(key, "*$") == 0)
 175                 snprintf(fullname, sizeof(fullname), "*%s", name);
 176         else if (strncmp(key, "$", 1) == 0)
 177                 snprintf(fullname, 256, "%s%s", name, key + 1);
 178         else
 179                 return;
 180 
 181         set_state(my_id, fullname, sym, (*value == '1') ? &terminated : &unterminated);
 182 }
 183 
 184 static void split_return_info(int return_id, char *return_ranges, struct expression *expr)
 185 {
 186         struct symbol *returned_sym;
 187         struct sm_state *tmp, *sm;
 188         const char *param_name;
 189         int param;
 190         int term;
 191 
 192         FOR_EACH_MY_SM(param_set_id, __get_cur_stree(), tmp) {
 193                 sm = get_sm_state(my_id, tmp->name, tmp->sym);
 194                 if (!sm)
 195                         continue;
 196                 term = sm_to_term(sm);
 197                 if (term < 0)
 198                         continue;
 199                 param = get_param_num_from_sym(tmp->sym);
 200                 if (param < 0)
 201                         continue;
 202 
 203                 param_name = get_param_name(sm);
 204                 if (!param_name)
 205                         continue;
 206                 if (strcmp(param_name, "$") == 0)
 207                         continue;
 208 
 209                 sql_insert_return_states(return_id, return_ranges, TERMINATED,
 210                                          param, param_name, term ? "1" : "0");
 211         } END_FOR_EACH_SM(tmp);
 212 
 213         returned_sym = expr_to_sym(expr);
 214         if (!returned_sym)
 215                 return;
 216 
 217         FOR_EACH_MY_SM(my_id, __get_cur_stree(), sm) {
 218                 if (sm->sym != returned_sym)
 219                         continue;
 220                 term = sm_to_term(sm);
 221                 if (term < 0)
 222                         continue;
 223                 param_name = get_param_name(sm);
 224                 if (!param_name)
 225                         continue;
 226                 sql_insert_return_states(return_id, return_ranges, TERMINATED,
 227                                          -1, param_name, term ? "1" : "0");
 228         } END_FOR_EACH_SM(sm);
 229 }
 230 
 231 static void return_info_terminated(struct expression *expr, int param, char *key, char *value)
 232 {
 233         struct expression *arg;
 234         char *name;
 235         struct symbol *sym;
 236 
 237         if (param == -1) {
 238                 arg = expr->left;
 239         } else {
 240                 struct expression *call = expr;
 241 
 242                 while (call->type == EXPR_ASSIGNMENT)
 243                         call = strip_expr(call->right);
 244                 if (call->type != EXPR_CALL)
 245                         return;
 246 
 247                 arg = get_argument_from_call_expr(call->args, param);
 248                 if (!arg)
 249                         return;
 250         }
 251 
 252         name = get_variable_from_key(arg, key, &sym);
 253         if (!name || !sym)
 254                 goto free;
 255 
 256         set_terminated_var_sym(name, sym, (*value == '1') ? &terminated : &unterminated);
 257 free:
 258         free_string(name);
 259 }
 260 
 261 bool is_nul_terminated_var_sym(const char *name, struct symbol *sym)
 262 {
 263         if (get_terminated_state_var_sym(name, sym) == &terminated)
 264                 return 1;
 265         return 0;
 266 }
 267 
 268 bool is_nul_terminated(struct expression *expr)
 269 {
 270         if (get_terminated_state(expr) == &terminated)
 271                 return 1;
 272         return 0;
 273 }
 274 
 275 static void match_strnlen_test(struct expression *expr)
 276 {
 277         struct expression *left, *tmp, *arg;
 278         int cnt;
 279 
 280         if (expr->type != EXPR_COMPARE)
 281                 return;
 282         if (expr->op != SPECIAL_EQUAL && expr->op != SPECIAL_NOTEQUAL)
 283                 return;
 284 
 285         left = strip_expr(expr->left);
 286         cnt = 0;
 287         while ((tmp = get_assigned_expr(left))) {
 288                 if (cnt++ > 3)
 289                         break;
 290                 left = tmp;
 291         }
 292 
 293         if (left->type != EXPR_CALL)
 294                 return;
 295         if (!sym_name_is("strnlen", left->fn))
 296                 return;
 297         arg = get_argument_from_call_expr(left->args, 0);
 298         set_true_false_states_expr(my_id, arg,
 299                         (expr->op == SPECIAL_EQUAL) ? &terminated : NULL,
 300                         (expr->op == SPECIAL_NOTEQUAL) ? &terminated : NULL);
 301         if (get_param_num(arg) >= 0)
 302                 set_true_false_states_expr(param_set_id, arg,
 303                                 (expr->op == SPECIAL_EQUAL) ? &terminated : NULL,
 304                                 (expr->op == SPECIAL_NOTEQUAL) ? &terminated : NULL);
 305 }
 306 
 307 void register_nul_terminator(int id)
 308 {
 309         my_id = id;
 310 
 311         add_hook(&match_nul_assign, ASSIGNMENT_HOOK);
 312         add_hook(&match_string_assign, ASSIGNMENT_HOOK);
 313 
 314         add_hook(&match_call_info, FUNCTION_CALL_HOOK);
 315         add_member_info_callback(my_id, struct_member_callback);
 316         add_split_return_callback(&split_return_info);
 317 
 318         select_caller_info_hook(caller_info_terminated, TERMINATED);
 319         select_return_states_hook(TERMINATED, return_info_terminated);
 320 
 321         add_hook(&match_strnlen_test, CONDITION_HOOK);
 322 }
 323 
 324 void register_nul_terminator_param_set(int id)
 325 {
 326         param_set_id = id;
 327 }