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
28static int my_id;
29
30STATE(freed);
31STATE(ok);
32
33static 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
39static 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
45static 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
62static 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
72static 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
95static 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
116static int ignored_params[16];
117
118static 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
136found:
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
157static 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
172static 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
203static 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
221static 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
240static 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);
268free:
269	free_string(name);
270}
271
272int 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
295int 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);
306free:
307	free_string(var);
308	return ret;
309}
310
311static 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
343void 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