xref: /illumos-gate/usr/src/tools/smatch/src/check_free_strict.c (revision 45ede40b2394db7967e59f19288fae9b62efd4aa)
1 /*
2  * Copyright (C) 2010 Dan Carpenter.
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  * check_memory() is getting too big and messy.
20  *
21  */
22 
23 #include <string.h>
24 #include "smatch.h"
25 #include "smatch_slist.h"
26 #include "smatch_extra.h"
27 
28 static int my_id;
29 
30 STATE(freed);
31 STATE(ok);
32 
33 static void ok_to_use(struct sm_state *sm, struct expression *mod_expr)
34 {
35 	if (sm->state != &ok)
36 		set_state(my_id, sm->name, sm->sym, &ok);
37 }
38 
39 static void pre_merge_hook(struct sm_state *cur, struct sm_state *other)
40 {
41 	if (is_impossible_path())
42 		set_state(my_id, cur->name, cur->sym, &ok);
43 }
44 
45 static struct smatch_state *unmatched_state(struct sm_state *sm)
46 {
47 	struct smatch_state *state;
48 	sval_t sval;
49 
50 	if (sm->state != &freed)
51 		return &undefined;
52 
53 	state = get_state(SMATCH_EXTRA, sm->name, sm->sym);
54 	if (!state)
55 		return &undefined;
56 	if (!estate_get_single_value(state, &sval) || sval.value != 0)
57 		return &undefined;
58 	/* It makes it easier to consider NULL pointers as freed.  */
59 	return &freed;
60 }
61 
62 static int is_freed(struct expression *expr)
63 {
64 	struct sm_state *sm;
65 
66 	sm = get_sm_state_expr(my_id, expr);
67 	if (sm && slist_has_state(sm->possible, &freed))
68 		return 1;
69 	return 0;
70 }
71 
72 static void match_symbol(struct expression *expr)
73 {
74 	struct expression *parent;
75 	char *name;
76 
77 	if (is_impossible_path())
78 		return;
79 	if (__in_fake_parameter_assign)
80 		return;
81 
82 	parent = expr_get_parent_expr(expr);
83 	while (parent && parent->type == EXPR_PREOP && parent->op == '(')
84 		parent = expr_get_parent_expr(parent);
85 	if (parent && parent->type == EXPR_PREOP && parent->op == '&')
86 		return;
87 
88 	if (!is_freed(expr))
89 		return;
90 	name = expr_to_var(expr);
91 	sm_warning("'%s' was already freed.", name);
92 	free_string(name);
93 }
94 
95 static void match_dereferences(struct expression *expr)
96 {
97 	char *name;
98 
99 	if (expr->type != EXPR_PREOP)
100 		return;
101 
102 	if (is_impossible_path())
103 		return;
104 	if (__in_fake_parameter_assign)
105 		return;
106 
107 	expr = strip_expr(expr->unop);
108 	if (!is_freed(expr))
109 		return;
110 	name = expr_to_var(expr);
111 	sm_error("dereferencing freed memory '%s'", name);
112 	set_state_expr(my_id, expr, &ok);
113 	free_string(name);
114 }
115 
116 static int ignored_params[16];
117 
118 static void set_ignored_params(struct expression *call)
119 {
120 	struct expression *arg;
121 	const char *p;
122 	int i;
123 
124 	memset(&ignored_params, 0, sizeof(ignored_params));
125 
126 	i = -1;
127 	FOR_EACH_PTR(call->args, arg) {
128 		i++;
129 		if (arg->type != EXPR_STRING)
130 			continue;
131 		goto found;
132 	} END_FOR_EACH_PTR(arg);
133 
134 	return;
135 
136 found:
137 	i++;
138 	p = arg->string->data;
139 	while ((p = strchr(p, '%'))) {
140 		if (i >= ARRAY_SIZE(ignored_params))
141 			return;
142 		p++;
143 		if (*p == '%') {
144 			p++;
145 			continue;
146 		}
147 		if (*p == '.')
148 			p++;
149 		if (*p == '*')
150 			i++;
151 		if (*p == 'p')
152 			ignored_params[i] = 1;
153 		i++;
154 	}
155 }
156 
157 static int is_free_func(struct expression *fn)
158 {
159 	char *name;
160 	int ret = 0;
161 
162 	name = expr_to_str(fn);
163 	if (!name)
164 		return 0;
165 	if (strstr(name, "free"))
166 		ret = 1;
167 	free_string(name);
168 
169 	return ret;
170 }
171 
172 static void match_call(struct expression *expr)
173 {
174 	struct expression *arg;
175 	char *name;
176 	int i;
177 
178 	if (is_impossible_path())
179 		return;
180 
181 	set_ignored_params(expr);
182 
183 	i = -1;
184 	FOR_EACH_PTR(expr->args, arg) {
185 		i++;
186 		if (!is_pointer(arg))
187 			continue;
188 		if (!is_freed(arg))
189 			continue;
190 		if (ignored_params[i])
191 			continue;
192 
193 		name = expr_to_var(arg);
194 		if (is_free_func(expr->fn))
195 			sm_error("double free of '%s'", name);
196 		else
197 			sm_warning("passing freed memory '%s'", name);
198 		set_state_expr(my_id, arg, &ok);
199 		free_string(name);
200 	} END_FOR_EACH_PTR(arg);
201 }
202 
203 static void match_return(struct expression *expr)
204 {
205 	char *name;
206 
207 	if (is_impossible_path())
208 		return;
209 
210 	if (!expr)
211 		return;
212 	if (!is_freed(expr))
213 		return;
214 
215 	name = expr_to_var(expr);
216 	sm_warning("returning freed memory '%s'", name);
217 	set_state_expr(my_id, expr, &ok);
218 	free_string(name);
219 }
220 
221 static void match_free(const char *fn, struct expression *expr, void *param)
222 {
223 	struct expression *arg;
224 
225 	if (is_impossible_path())
226 		return;
227 
228 	arg = get_argument_from_call_expr(expr->args, PTR_INT(param));
229 	if (!arg)
230 		return;
231 	if (is_freed(arg)) {
232 		char *name = expr_to_var(arg);
233 
234 		sm_error("double free of '%s'", name);
235 		free_string(name);
236 	}
237 	set_state_expr(my_id, arg, &freed);
238 }
239 
240 static void set_param_freed(struct expression *expr, int param, char *key, char *value)
241 {
242 	struct expression *arg;
243 	char *name;
244 	struct symbol *sym;
245 	struct sm_state *sm;
246 
247 	while (expr->type == EXPR_ASSIGNMENT)
248 		expr = strip_expr(expr->right);
249 	if (expr->type != EXPR_CALL)
250 		return;
251 
252 	arg = get_argument_from_call_expr(expr->args, param);
253 	if (!arg)
254 		return;
255 	name = get_variable_from_key(arg, key, &sym);
256 	if (!name || !sym)
257 		goto free;
258 
259 	if (!is_impossible_path()) {
260 		sm = get_sm_state(my_id, name, sym);
261 		if (sm && slist_has_state(sm->possible, &freed)) {
262 			sm_warning("'%s' double freed", name);
263 			set_state(my_id, name, sym, &ok);  /* fixme: doesn't silence anything.  I know */
264 		}
265 	}
266 
267 	set_state(my_id, name, sym, &freed);
268 free:
269 	free_string(name);
270 }
271 
272 int parent_is_free_var_sym_strict(const char *name, struct symbol *sym)
273 {
274 	char buf[256];
275 	char *start;
276 	char *end;
277 	struct smatch_state *state;
278 
279 	strncpy(buf, name, sizeof(buf) - 1);
280 	buf[sizeof(buf) - 1] = '\0';
281 
282 	start = &buf[0];
283 	while ((*start == '&'))
284 		start++;
285 
286 	while ((end = strrchr(start, '-'))) {
287 		*end = '\0';
288 		state = __get_state(my_id, start, sym);
289 		if (state == &freed)
290 			return 1;
291 	}
292 	return 0;
293 }
294 
295 int parent_is_free_strict(struct expression *expr)
296 {
297 	struct symbol *sym;
298 	char *var;
299 	int ret = 0;
300 
301 	expr = strip_expr(expr);
302 	var = expr_to_var_sym(expr, &sym);
303 	if (!var || !sym)
304 		goto free;
305 	ret = parent_is_free_var_sym_strict(var, sym);
306 free:
307 	free_string(var);
308 	return ret;
309 }
310 
311 static void match_untracked(struct expression *call, int param)
312 {
313 	struct state_list *slist = NULL;
314 	struct expression *arg;
315 	struct sm_state *sm;
316 	char *name;
317 	char buf[64];
318 	int len;
319 
320 	arg = get_argument_from_call_expr(call->args, param);
321 	if (!arg)
322 		return;
323 
324 	name = expr_to_var(arg);
325 	if (!name)
326 		return;
327 	snprintf(buf, sizeof(buf), "%s->", name);
328 	free_string(name);
329 	len = strlen(buf);
330 
331 	FOR_EACH_MY_SM(my_id, __get_cur_stree(), sm) {
332 		if (strncmp(sm->name, buf, len) == 0)
333 			add_ptr_list(&slist, sm);
334 	} END_FOR_EACH_SM(sm);
335 
336 	FOR_EACH_PTR(slist, sm) {
337 		set_state(sm->owner, sm->name, sm->sym, &ok);
338 	} END_FOR_EACH_PTR(sm);
339 
340 	free_slist(&slist);
341 }
342 
343 void check_free_strict(int id)
344 {
345 	my_id = id;
346 
347 	if (option_project != PROJ_KERNEL)
348 		return;
349 
350 	add_function_hook("kfree", &match_free, INT_PTR(0));
351 	add_function_hook("kmem_cache_free", &match_free, INT_PTR(1));
352 
353 	if (option_spammy)
354 		add_hook(&match_symbol, SYM_HOOK);
355 	add_hook(&match_dereferences, DEREF_HOOK);
356 	add_hook(&match_call, FUNCTION_CALL_HOOK);
357 	add_hook(&match_return, RETURN_HOOK);
358 
359 	add_modification_hook_late(my_id, &ok_to_use);
360 	add_pre_merge_hook(my_id, &pre_merge_hook);
361 	add_unmatched_state_hook(my_id, &unmatched_state);
362 
363 	select_return_states_hook(PARAM_FREED, &set_param_freed);
364 	add_untracked_param_hook(&match_untracked);
365 }
366