extra: move some code out of match_comparison()
[smatch.git] / check_overflow.c
blobe578d228b87b2409dc41a045cc44f30730fa0eef
1 /*
2 * smatch/check_overflow.c
4 * Copyright (C) 2010 Dan Carpenter.
6 * Licensed under the Open Software License version 1.1
8 */
10 #include <stdlib.h>
11 #include "parse.h"
12 #include "smatch.h"
13 #include "smatch_slist.h"
14 #include "smatch_extra.h"
16 struct bound {
17 int param;
18 int size;
22 * This check has two smatch IDs.
23 * my_used_id - keeps a record of array offsets that have been used.
24 * If the code checks that they are within bounds later on,
25 * we complain about using an array offset before checking
26 * that it is within bounds.
28 static int my_used_id;
30 static struct symbol *this_func;
32 static void match_function_def(struct symbol *sym)
34 this_func = sym;
37 struct limiter {
38 int buf_arg;
39 int limit_arg;
41 static struct limiter b0_l2 = {0, 2};
42 static struct limiter b1_l2 = {1, 2};
44 static void delete(const char *name, struct symbol *sym, struct expression *expr, void *unused)
46 delete_state(my_used_id, name, sym);
49 static int definitely_just_used_as_limiter(struct expression *array, struct expression *offset)
51 long long val;
52 struct expression *tmp;
53 int step = 0;
54 int dot_ops = 0;
56 if (!get_implied_value(offset, &val))
57 return 0;
58 if (get_array_size(array) != val)
59 return 0;
61 FOR_EACH_PTR_REVERSE(big_expression_stack, tmp) {
62 if (step == 0) {
63 step = 1;
64 continue;
66 if (tmp->type == EXPR_PREOP && tmp->op == '(')
67 continue;
68 if (tmp->op == '.' && !dot_ops++)
69 continue;
70 if (step == 1 && tmp->op == '&') {
71 step = 2;
72 continue;
74 if (step == 2 && tmp->type == EXPR_COMPARE)
75 return 1;
76 return 0;
77 } END_FOR_EACH_PTR_REVERSE(tmp);
78 return 0;
81 static void array_check(struct expression *expr)
83 struct expression *array_expr;
84 int array_size;
85 struct expression *offset;
86 long long max;
87 char *name;
89 expr = strip_expr(expr);
90 if (!is_array(expr))
91 return;
93 array_expr = strip_parens(expr->unop->left);
94 array_size = get_array_size(array_expr);
95 if (!array_size || array_size == 1)
96 return;
98 offset = get_array_offset(expr);
99 if (!get_fuzzy_max(offset, &max)) {
100 if (getting_address())
101 return;
102 set_state_expr(my_used_id, offset, alloc_state_num(array_size));
103 add_modification_hook_expr(my_used_id, offset, &delete, NULL);
104 } else if (array_size <= max) {
105 const char *level = "error";
107 if (getting_address())
108 level = "warn";
110 if (definitely_just_used_as_limiter(array_expr, offset))
111 return;
113 if (!option_spammy) {
114 struct smatch_state *state;
116 state = get_state_expr(SMATCH_EXTRA, offset);
117 if (state && is_whole_range(state))
118 return;
121 name = get_variable_from_expr_complex(array_expr, NULL);
122 /* Blast. Smatch can't figure out glibc's strcmp __strcmp_cg()
123 * so it prints an error every time you compare to a string
124 * literal array with 4 or less chars.
126 if (name && strcmp(name, "__s1") && strcmp(name, "__s2")) {
127 sm_msg("%s: buffer overflow '%s' %d <= %lld",
128 level, name, array_size, max);
130 free_string(name);
134 static void match_condition(struct expression *expr)
136 int left;
137 long long val;
138 struct state_list *slist;
139 struct sm_state *tmp;
140 int boundary;
142 if (!expr || expr->type != EXPR_COMPARE)
143 return;
144 if (get_macro_name(&expr->pos))
145 return;
146 if (get_implied_value(expr->left, &val))
147 left = 1;
148 else if (get_implied_value(expr->right, &val))
149 left = 0;
150 else
151 return;
153 if (left)
154 slist = get_possible_states_expr(my_used_id, expr->right);
155 else
156 slist = get_possible_states_expr(my_used_id, expr->left);
157 if (!slist)
158 return;
159 FOR_EACH_PTR(slist, tmp) {
160 if (tmp->state == &merged)
161 continue;
162 boundary = PTR_INT(tmp->state->data);
163 boundary -= val;
164 if (boundary < 1 && boundary > -1) {
165 char *name;
167 name = get_variable_from_expr((left ? expr->right : expr->left), NULL);
168 sm_msg("error: testing array offset '%s' after use.", name);
169 return;
171 } END_FOR_EACH_PTR(tmp);
174 static void match_strcpy(const char *fn, struct expression *expr, void *unused)
176 struct expression *dest;
177 struct expression *data;
178 char *dest_name = NULL;
179 char *data_name = NULL;
180 int dest_size;
181 int data_size;
183 dest = get_argument_from_call_expr(expr->args, 0);
184 data = get_argument_from_call_expr(expr->args, 1);
185 dest_size = get_array_size_bytes(dest);
186 data_size = get_array_size_bytes(data);
188 if (!dest_size)
189 return;
191 /* If the size of both arrays is known and the destination
192 * buffer is larger than the source buffer, we're okay.
194 if (data_size && dest_size >= data_size)
195 return;
197 dest_name = get_variable_from_expr_complex(dest, NULL);
198 data_name = get_variable_from_expr_complex(data, NULL);
200 if (data_size)
201 sm_msg("error: %s() '%s' too large for '%s' (%d vs %d)",
202 fn, data_name, dest_name, data_size, dest_size);
203 else if (option_spammy)
204 sm_msg("warn: %s() '%s' of unknown size might be too large for '%s'",
205 fn, data_name, dest_name);
207 free_string(dest_name);
208 free_string(data_name);
211 static void match_snprintf(const char *fn, struct expression *expr, void *unused)
213 struct expression *dest;
214 struct expression *dest_size_expr;
215 struct expression *format_string;
216 struct expression *data;
217 char *data_name = NULL;
218 int dest_size;
219 long long limit_size;
220 char *format;
221 int data_size;
223 dest = get_argument_from_call_expr(expr->args, 0);
224 dest_size_expr = get_argument_from_call_expr(expr->args, 1);
225 format_string = get_argument_from_call_expr(expr->args, 2);
226 data = get_argument_from_call_expr(expr->args, 3);
228 dest_size = get_array_size_bytes(dest);
229 if (!get_implied_value(dest_size_expr, &limit_size))
230 return;
231 if (dest_size && dest_size < limit_size)
232 sm_msg("error: snprintf() is printing too much %lld vs %d", limit_size, dest_size);
233 format = get_variable_from_expr(format_string, NULL);
234 if (!format)
235 return;
236 if (strcmp(format, "\"%s\""))
237 goto free;
238 data_name = get_variable_from_expr_complex(data, NULL);
239 data_size = get_array_size_bytes(data);
240 if (limit_size < data_size)
241 sm_msg("error: snprintf() chops off the last chars of '%s': %d vs %lld",
242 data_name, data_size, limit_size);
243 free:
244 free_string(data_name);
245 free_string(format);
248 static void match_sprintf(const char *fn, struct expression *expr, void *unused)
250 struct expression *dest;
251 struct expression *format_string;
252 struct expression *data;
253 char *data_name = NULL;
254 int dest_size;
255 char *format;
256 int data_size;
258 dest = get_argument_from_call_expr(expr->args, 0);
259 format_string = get_argument_from_call_expr(expr->args, 1);
260 data = get_argument_from_call_expr(expr->args, 2);
262 dest_size = get_array_size_bytes(dest);
263 if (!dest_size)
264 return;
265 format = get_variable_from_expr(format_string, NULL);
266 if (!format)
267 return;
268 if (strcmp(format, "\"%s\""))
269 goto free;
270 data_name = get_variable_from_expr_complex(data, NULL);
271 data_size = get_array_size_bytes(data);
272 if (dest_size < data_size)
273 sm_msg("error: sprintf() copies too much data from '%s': %d vs %d",
274 data_name, data_size, dest_size);
275 free:
276 free_string(data_name);
277 free_string(format);
280 static void match_limited(const char *fn, struct expression *expr, void *_limiter)
282 struct limiter *limiter = (struct limiter *)_limiter;
283 struct expression *dest;
284 struct expression *data;
285 char *dest_name = NULL;
286 long long needed;
287 int has;
289 dest = get_argument_from_call_expr(expr->args, limiter->buf_arg);
290 data = get_argument_from_call_expr(expr->args, limiter->limit_arg);
291 if (!get_fuzzy_max(data, &needed))
292 return;
293 has = get_array_size_bytes(dest);
294 if (!has)
295 return;
296 if (has >= needed)
297 return;
299 dest_name = get_variable_from_expr_complex(dest, NULL);
300 sm_msg("error: %s() '%s' too small (%d vs %lld)", fn, dest_name, has, needed);
301 free_string(dest_name);
304 void check_overflow(int id)
306 my_used_id = id;
307 add_hook(&match_function_def, FUNC_DEF_HOOK);
308 add_hook(&array_check, OP_HOOK);
309 add_hook(&match_condition, CONDITION_HOOK);
310 add_function_hook("strcpy", &match_strcpy, NULL);
311 add_function_hook("snprintf", &match_snprintf, NULL);
312 add_function_hook("sprintf", &match_sprintf, NULL);
313 if (option_project == PROJ_KERNEL) {
314 add_function_hook("copy_to_user", &match_limited, &b0_l2);
315 add_function_hook("copy_to_user", &match_limited, &b1_l2);
316 add_function_hook("_copy_to_user", &match_limited, &b0_l2);
317 add_function_hook("_copy_to_user", &match_limited, &b1_l2);
318 add_function_hook("__copy_to_user", &match_limited, &b0_l2);
319 add_function_hook("__copy_to_user", &match_limited, &b1_l2);
320 add_function_hook("copy_from_user", &match_limited, &b0_l2);
321 add_function_hook("copy_from_user", &match_limited, &b1_l2);
322 add_function_hook("_copy_from_user", &match_limited, &b0_l2);
323 add_function_hook("_copy_from_user", &match_limited, &b1_l2);
324 add_function_hook("__copy_from_user", &match_limited, &b0_l2);
325 add_function_hook("__copy_from_user", &match_limited, &b1_l2);