smatch: save less stuff, run faster
[smatch.git] / smatch_points_to_user_data.c
blobe3435f98587c2632fa6b728bacceaedbd05dff2e
1 /*
2 * Copyright (C) 2020 Oracle.
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.
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.
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
19 * The problem here is that we can have:
21 * p = skb->data;
23 * In the olden days we would just set "*p = 0-255" which meant that it pointed
24 * to user data. But then if we say "if (*p == 11) {" that means that "*p" is
25 * not user data any more, so then "*(p + 1)" is marked as not user data but it
26 * is.
28 * So now we've separated out the stuff that points to a user_buf from the other
29 * user data.
31 * There is a further complication because what if "p" points to a struct? In
32 * that case all the struct members are handled by smatch_kernel_user_data.c
33 * but we still need to keep in mind that "*(p + 1)" is user data. I'm not
34 * totally 100% sure how this will work.
36 * Generally a user pointer should be a void pointer, or an array etc. But if
37 * it points to a struct that can only be used for pointer math.
41 #include "smatch.h"
42 #include "smatch_slist.h"
43 #include "smatch_extra.h"
45 static int my_id;
46 STATE(user_data);
48 static const char *returns_pointer_to_user_data[] = {
49 "nlmsg_data", "nla_data", "memdup_user", "kmap_atomic", "skb_network_header",
50 "cfg80211_find_elem_match", "ieee80211_bss_get_elem", "cfg80211_find_elem",
51 "ieee80211_bss_get_ie",
54 bool is_skb_data(struct expression *expr)
56 struct symbol *sym;
58 expr = strip_expr(expr);
59 if (!expr)
60 return false;
62 if (expr->type == EXPR_BINOP && expr->op == '+')
63 return is_skb_data(expr->left);
65 expr = strip_expr(expr);
66 if (!expr)
67 return false;
68 if (expr->type != EXPR_DEREF || expr->op != '.')
69 return false;
71 if (!expr->member)
72 return false;
73 if (strcmp(expr->member->name, "data") != 0)
74 return false;
76 sym = expr_to_sym(expr->deref);
77 if (!sym)
78 return false;
79 sym = get_real_base_type(sym);
80 if (!sym || sym->type != SYM_PTR)
81 return false;
82 sym = get_real_base_type(sym);
83 if (!sym || sym->type != SYM_STRUCT || !sym->ident)
84 return false;
85 if (strcmp(sym->ident->name, "sk_buff") != 0)
86 return false;
88 return true;
91 bool is_user_data_fn(struct symbol *fn)
93 int i;
95 if (!fn || !fn->ident)
96 return false;
98 for (i = 0; i < ARRAY_SIZE(returns_pointer_to_user_data); i++) {
99 if (strcmp(fn->ident->name, returns_pointer_to_user_data[i]) == 0) {
100 // func_gets_user_data = true;
101 return true;
104 return false;
107 static bool is_points_to_user_data_fn(struct expression *expr)
109 expr = strip_expr(expr);
110 if (!expr || expr->type != EXPR_CALL || expr->fn->type != EXPR_SYMBOL ||
111 !expr->fn->symbol)
112 return false;
113 return is_user_data_fn(expr->fn->symbol);
116 static bool is_array_of_user_data(struct expression *expr)
118 if (expr->type == EXPR_PREOP && expr->op == '&') {
119 expr = strip_expr(expr->unop);
120 if (expr->type == EXPR_PREOP && expr->op == '*')
121 expr = strip_expr(expr->unop);
124 if (expr->type == EXPR_BINOP && expr->op == '+') {
125 if (points_to_user_data(expr->left))
126 return true;
127 if (points_to_user_data(expr->right))
128 return true;
131 return false;
134 bool points_to_user_data(struct expression *expr)
136 struct sm_state *sm;
138 expr = strip_expr(expr);
139 if (!expr)
140 return false;
142 if (expr->type == EXPR_ASSIGNMENT)
143 return points_to_user_data(expr->left);
145 if (is_array_of_user_data(expr))
146 return true;
148 if (is_skb_data(expr))
149 return true;
151 if (is_points_to_user_data_fn(expr))
152 return true;
154 sm = get_sm_state_expr(my_id, expr);
155 if (sm && slist_has_state(sm->possible, &user_data))
156 return true;
157 return false;
160 void set_points_to_user_data(struct expression *expr)
162 set_state_expr(my_id, expr, &user_data);
165 static bool handle_memcpy_fake_assignments(struct expression *expr)
167 struct expression *left = strip_parens(expr->left);
168 struct expression *right = strip_parens(expr->right);
170 /* memcpy(array, src, sizeof(array)) gets turned into *array = *src; */
172 if (left->type != EXPR_PREOP || left->op != '*')
173 return false;
174 if (right->type != EXPR_PREOP || right->op != '*')
175 return false;
176 left = strip_expr(left->unop);
177 right = strip_expr(right->unop);
179 if (points_to_user_data(right)) {
180 set_points_to_user_data(left);
181 return true;
183 return false;
186 static void match_assign(struct expression *expr)
188 if (!is_ptr_type(get_type(expr->left)))
189 return;
191 if (points_to_user_data(expr->right)) {
192 set_points_to_user_data(expr->left);
193 return;
196 if (handle_memcpy_fake_assignments(expr))
197 return;
199 if (get_state_expr(my_id, expr->left))
200 set_state_expr(my_id, expr->left, &undefined);
203 static void return_info_callback(int return_id, char *return_ranges,
204 struct expression *returned_expr,
205 int param,
206 const char *printed_name,
207 struct sm_state *sm)
209 int type = USER_PTR_SET;
211 if (!slist_has_state(sm->possible, &user_data))
212 return;
214 if (param >= 0) {
215 if (strcmp(printed_name, "$") == 0)
216 return;
217 // FIXME: This isn't ideal. There should be a state_was_set()
218 // functions.
219 if (!param_was_set_var_sym(sm->name, sm->sym))
220 return;
221 } else {
222 if (!param_was_set_var_sym(sm->name, sm->sym))
223 type = USER_PTR;
225 if (parent_is_gone_var_sym(sm->name, sm->sym))
226 return;
228 sql_insert_return_states(return_id, return_ranges, type,
229 param, printed_name, "");
232 static void returns_user_ptr_helper(struct expression *expr, int param, char *key, char *value, bool set)
234 struct expression *arg;
235 struct expression *call;
236 char *name;
237 struct symbol *sym;
239 call = expr;
240 while (call->type == EXPR_ASSIGNMENT)
241 call = strip_expr(call->right);
242 if (call->type != EXPR_CALL)
243 return;
245 if (!set && !we_pass_user_data(call))
246 return;
248 if (param == -1) {
249 if (expr->type != EXPR_ASSIGNMENT) {
250 /* Nothing to do. Fake assignments should handle it */
251 return;
253 arg = expr->left;
254 goto set_user;
257 arg = get_argument_from_call_expr(call->args, param);
258 if (!arg)
259 return;
260 set_user:
261 name = get_variable_from_key(arg, key, &sym);
262 if (!name || !sym)
263 goto free;
264 set_state(my_id, name, sym, &user_data);
265 free:
266 free_string(name);
269 static void returns_user_ptr(struct expression *expr, int param, char *key, char *value)
271 returns_user_ptr_helper(expr, param, key, value, false);
274 static void returns_user_ptr_set(struct expression *expr, int param, char *key, char *value)
276 returns_user_ptr_helper(expr, param, key, value, true);
279 static void set_param_user_ptr(const char *name, struct symbol *sym, char *key, char *value)
281 struct expression *expr;
282 char *fullname;
284 expr = symbol_expression(sym);
285 fullname = get_variable_from_key(expr, key, NULL);
286 if (!fullname)
287 return;
288 set_state(my_id, fullname, sym, &user_data);
291 static void caller_info_callback(struct expression *call, int param, char *printed_name, struct sm_state *sm)
293 if (!slist_has_state(sm->possible, &user_data))
294 return;
295 sql_insert_caller_info(call, USER_PTR, param, printed_name, "");
298 void register_points_to_user_data(int id)
300 my_id = id;
302 if (option_project != PROJ_KERNEL)
303 return;
305 add_hook(&match_assign, ASSIGNMENT_HOOK);
307 add_caller_info_callback(my_id, caller_info_callback);
308 add_return_info_callback(my_id, return_info_callback);
310 select_caller_info_hook(set_param_user_ptr, USER_PTR);
311 select_return_states_hook(USER_PTR, &returns_user_ptr);
312 select_return_states_hook(USER_PTR_SET, &returns_user_ptr_set);