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