xref: /illumos-gate/usr/src/tools/smatch/src/smatch_nul_terminator.c (revision 1b58875ad7966cf2c85ee8e92f3da04f0a3b2f7a)
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(struct expression *expr)
73 {
74 	struct sm_state *sm, *tmp;
75 
76 	if (!expr)
77 		return NULL;
78 	if (expr->type == EXPR_STRING)
79 		return &terminated;
80 	sm = get_sm_state_expr(my_id, expr);
81 	if (!sm)
82 		return NULL;
83 	if (sm->state == &terminated || sm->state == &unterminated)
84 		return sm->state;
85 
86 	FOR_EACH_PTR(sm->possible, tmp) {
87 		if (tmp->state == &unterminated)
88 			return &unterminated;
89 	} END_FOR_EACH_PTR(tmp);
90 
91 	return NULL;
92 }
93 
94 static void match_string_assign(struct expression *expr)
95 {
96 	struct smatch_state *state;
97 
98 	if (expr->op != '=')
99 		return;
100 	state = get_terminated_state(expr->right);
101 	if (!state)
102 		return;
103 	set_terminated(expr->left, state);
104 }
105 
106 static int sm_to_term(struct sm_state *sm)
107 {
108 	struct sm_state *tmp;
109 
110 	if (!sm)
111 		return -1;
112 	if (sm->state == &terminated)
113 		return 1;
114 
115 	FOR_EACH_PTR(sm->possible, tmp) {
116 		if (tmp->state == &unterminated)
117 			return 0;
118 	} END_FOR_EACH_PTR(tmp);
119 
120 	return -1;
121 }
122 
123 static void struct_member_callback(struct expression *call, int param, char *printed_name, struct sm_state *sm)
124 {
125 	int term;
126 
127 	term = sm_to_term(sm);
128 	if (term < 0)
129 		return;
130 
131 	sql_insert_caller_info(call, TERMINATED, param, printed_name, term ? "1" : "0");
132 }
133 
134 static void match_call_info(struct expression *expr)
135 {
136 	struct smatch_state *state;
137 	struct expression *arg;
138 	int i;
139 
140 	i = -1;
141 	FOR_EACH_PTR(expr->args, arg) {
142 		i++;
143 
144 		state = get_terminated_state(arg);
145 		if (!state)
146 			continue;
147 		sql_insert_caller_info(expr, TERMINATED, i, "$",
148 				       (state == &terminated) ? "1" : "0");
149 	} END_FOR_EACH_PTR(arg);
150 }
151 
152 static void caller_info_terminated(const char *name, struct symbol *sym, char *key, char *value)
153 {
154 	char fullname[256];
155 
156 	if (strcmp(key, "*$") == 0)
157 		snprintf(fullname, sizeof(fullname), "*%s", name);
158 	else if (strncmp(key, "$", 1) == 0)
159 		snprintf(fullname, 256, "%s%s", name, key + 1);
160 	else
161 		return;
162 
163 	set_state(my_id, fullname, sym, (*value == '1') ? &terminated : &unterminated);
164 }
165 
166 static void split_return_info(int return_id, char *return_ranges, struct expression *expr)
167 {
168 	struct symbol *returned_sym;
169 	struct sm_state *tmp, *sm;
170 	const char *param_name;
171 	int param;
172 	int term;
173 
174 	FOR_EACH_MY_SM(param_set_id, __get_cur_stree(), tmp) {
175 		sm = get_sm_state(my_id, tmp->name, tmp->sym);
176 		if (!sm)
177 			continue;
178 		term = sm_to_term(sm);
179 		if (term < 0)
180 			continue;
181 		param = get_param_num_from_sym(tmp->sym);
182 		if (param < 0)
183 			continue;
184 
185 		param_name = get_param_name(sm);
186 		if (!param_name)
187 			continue;
188 		if (strcmp(param_name, "$") == 0)
189 			continue;
190 
191 		sql_insert_return_states(return_id, return_ranges, TERMINATED,
192 					 param, param_name, term ? "1" : "0");
193 	} END_FOR_EACH_SM(tmp);
194 
195 	returned_sym = expr_to_sym(expr);
196 	if (!returned_sym)
197 		return;
198 
199 	FOR_EACH_MY_SM(my_id, __get_cur_stree(), sm) {
200 		if (sm->sym != returned_sym)
201 			continue;
202 		term = sm_to_term(sm);
203 		if (term < 0)
204 			continue;
205 		param_name = get_param_name(sm);
206 		if (!param_name)
207 			continue;
208 		sql_insert_return_states(return_id, return_ranges, TERMINATED,
209 					 -1, param_name, term ? "1" : "0");
210 	} END_FOR_EACH_SM(sm);
211 }
212 
213 static void return_info_terminated(struct expression *expr, int param, char *key, char *value)
214 {
215 	struct expression *arg;
216 	char *name;
217 	struct symbol *sym;
218 
219 	if (param == -1) {
220 		arg = expr->left;
221 	} else {
222 		struct expression *call = expr;
223 
224 		while (call->type == EXPR_ASSIGNMENT)
225 			call = strip_expr(call->right);
226 		if (call->type != EXPR_CALL)
227 			return;
228 
229 		arg = get_argument_from_call_expr(call->args, param);
230 		if (!arg)
231 			return;
232 	}
233 
234 	name = get_variable_from_key(arg, key, &sym);
235 	if (!name || !sym)
236 		goto free;
237 
238 	set_terminated_var_sym(name, sym, (*value == '1') ? &terminated : &unterminated);
239 free:
240 	free_string(name);
241 }
242 
243 bool is_nul_terminated(struct expression *expr)
244 {
245 	if (get_terminated_state(expr) == &terminated)
246 		return 1;
247 	return 0;
248 }
249 
250 void register_nul_terminator(int id)
251 {
252 	my_id = id;
253 
254 	add_hook(&match_nul_assign, ASSIGNMENT_HOOK);
255 	add_hook(&match_string_assign, ASSIGNMENT_HOOK);
256 
257 	add_hook(&match_call_info, FUNCTION_CALL_HOOK);
258 	add_member_info_callback(my_id, struct_member_callback);
259 	add_split_return_callback(&split_return_info);
260 
261 	select_caller_info_hook(caller_info_terminated, TERMINATED);
262 	select_return_states_hook(TERMINATED, return_info_terminated);
263 }
264 
265 void register_nul_terminator_param_set(int id)
266 {
267 	param_set_id = id;
268 }
269