sm_hash: export str_to_llu_hash_helper()
[smatch.git] / smatch_points_to_user_data.c
blobf79c60d24fe47ed0b4780195d31701bc2bec20b9
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);
47 STATE(user_data_set);
49 struct user_fn_info {
50 const char *name;
51 int type;
52 int param;
53 const char *key;
56 static struct user_fn_info func_table[] = {
57 { "(struct ksmbd_transport_ops)->read", USER_DATA, 1, "$" },
60 static struct user_fn_info call_table[] = {
61 { "__handle_ksmbd_work", USER_DATA, 0, "$->request_buf" },
64 static const char *returns_pointer_to_user_data[] = {
65 "nlmsg_data", "nla_data", "memdup_user", "kmap_atomic", "skb_network_header",
66 "cfg80211_find_elem_match", "ieee80211_bss_get_elem", "cfg80211_find_elem",
67 "ieee80211_bss_get_ie",
70 bool is_skb_data(struct expression *expr)
72 struct symbol *sym;
74 expr = strip_expr(expr);
75 if (!expr)
76 return false;
77 if (expr->type != EXPR_DEREF)
78 return false;
80 if (!expr->member)
81 return false;
82 if (strcmp(expr->member->name, "data") != 0)
83 return false;
85 sym = get_type(expr->deref);
86 if (!sym)
87 return false;
88 if (sym->type == SYM_PTR)
89 sym = get_real_base_type(sym);
90 if (!sym || sym->type != SYM_STRUCT || !sym->ident)
91 return false;
92 if (strcmp(sym->ident->name, "sk_buff") != 0)
93 return false;
95 return true;
98 bool is_user_data_fn(struct symbol *fn)
100 int i;
102 if (!fn || !fn->ident)
103 return false;
105 for (i = 0; i < ARRAY_SIZE(returns_pointer_to_user_data); i++) {
106 if (strcmp(fn->ident->name, returns_pointer_to_user_data[i]) == 0) {
107 // func_gets_user_data = true;
108 return true;
111 return false;
114 static struct expression *returns_user_data_fn;
115 static bool is_points_to_user_data_fn(struct expression *expr)
117 expr = strip_expr(expr);
118 if (returns_user_data_fn && expr == returns_user_data_fn)
119 return true;
120 if (!expr || expr->type != EXPR_CALL || expr->fn->type != EXPR_SYMBOL ||
121 !expr->fn->symbol)
122 return false;
123 return is_user_data_fn(expr->fn->symbol);
126 static bool is_array_of_user_data(struct expression *expr)
128 struct expression *deref;
129 struct symbol *type;
131 if (expr->type == EXPR_PREOP && expr->op == '&') {
132 expr = strip_expr(expr->unop);
133 if (expr->type == EXPR_PREOP && expr->op == '*')
134 expr = strip_expr(expr->unop);
137 /* This is for array elements &foo->data[4] */
138 if (expr->type == EXPR_BINOP && expr->op == '+') {
139 if (points_to_user_data(expr->left))
140 return true;
141 if (points_to_user_data(expr->right))
142 return true;
145 /* This is for if you have: foo = skb->data; frob(foo->array); */
146 type = get_type(expr);
147 if (!type || type->type != SYM_ARRAY)
148 return false;
150 if (expr->type != EXPR_DEREF)
151 return false;
152 deref = strip_expr(expr->deref);
153 if (deref->type != EXPR_PREOP || deref->op != '*')
154 return false;
155 deref = strip_expr(deref->unop);
156 return points_to_user_data(deref);
159 static struct expression *remove_addr_stuff(struct expression *expr)
161 /* take "&foo->bar" and return "foo" */
162 expr = strip_expr(expr);
163 if (expr->type != EXPR_PREOP || expr->op != '&')
164 return expr;
165 expr = strip_expr(expr->unop);
166 while (expr && expr->type == EXPR_DEREF) {
167 expr = strip_expr(expr->deref);
168 if (expr->op == '.')
169 continue;
170 else
171 break;
173 if (expr->type == EXPR_PREOP && expr->op == '*')
174 expr = strip_expr(expr->unop);
175 return expr;
178 static bool math_points_to_user_data(struct expression *expr)
180 struct sm_state *sm;
182 // TODO: is_array_of_user_data() should probably be handled here
184 if (expr->type == EXPR_BINOP && expr->op == '+')
185 return math_points_to_user_data(expr->left);
187 expr = remove_addr_stuff(expr);
189 sm = get_sm_state_expr(my_id, expr);
190 if (!sm)
191 return false;
192 if (slist_has_state(sm->possible, &user_data) ||
193 slist_has_state(sm->possible, &user_data_set))
194 return true;
195 return false;
198 bool points_to_user_data(struct expression *expr)
200 struct sm_state *sm;
202 expr = strip_expr(expr);
203 if (!expr)
204 return false;
206 if (expr->type == EXPR_POSTOP)
207 expr = strip_expr(expr->unop);
209 if (is_fake_call(expr))
210 return false;
212 if (expr->type == EXPR_ASSIGNMENT)
213 return points_to_user_data(expr->left);
215 if (is_array_of_user_data(expr))
216 return true;
218 if (expr->type == EXPR_BINOP && expr->op == '+')
219 return math_points_to_user_data(expr);
221 if (is_skb_data(expr))
222 return true;
224 if (is_points_to_user_data_fn(expr))
225 return true;
227 // FIXME if you have a struct pointer p then p->foo should be handled
228 // by smatch_kernel_user_data.c but if you have (p + 1)->foo then this
229 // should be handled here.
230 sm = get_sm_state_expr(my_id, expr);
231 if (!sm)
232 return false;
233 if (slist_has_state(sm->possible, &user_data) ||
234 slist_has_state(sm->possible, &user_data_set))
235 return true;
236 return false;
239 void set_points_to_user_data(struct expression *expr, bool is_new)
241 struct expression *tmp;
243 tmp = get_assigned_expr(expr);
244 if (tmp)
245 set_state_expr(my_id, tmp, is_new ? &user_data_set : &user_data);
246 set_state_expr(my_id, expr, is_new ? &user_data_set : &user_data);
249 static void match_assign(struct expression *expr)
251 if (is_fake_call(expr->right))
252 return;
254 if (!is_ptr_type(get_type(expr->left)))
255 return;
257 if (points_to_user_data(expr->right)) {
258 set_state_expr(my_id, expr->left, &user_data);
259 return;
262 if (get_state_expr(my_id, expr->left))
263 set_state_expr(my_id, expr->left, &undefined);
266 static void match_memcpy(const char *fn, struct expression *expr, void *_unused)
268 struct expression *dest, *src;
270 dest = get_argument_from_call_expr(expr->args, 0);
271 src = get_argument_from_call_expr(expr->args, 1);
273 if (points_to_user_data(src)) {
274 set_state_expr(my_id, expr->left, &user_data_set);
275 return;
278 if (get_state_expr(my_id, dest))
279 set_state_expr(my_id, dest, &undefined);
282 static void match_user_copy(const char *fn, struct expression *expr, void *_unused)
284 struct expression *dest, *size;
285 sval_t sval;
287 dest = get_argument_from_call_expr(expr->args, 0);
288 dest = strip_expr(dest);
289 if (!dest)
290 return;
292 size = get_argument_from_call_expr(expr->args, 2);
293 if (get_implied_value(size, &sval))
294 return;
296 set_state_expr(my_id, dest, &user_data_set);
299 static void return_info_callback(int return_id, char *return_ranges,
300 struct expression *returned_expr,
301 int param,
302 const char *printed_name,
303 struct sm_state *sm)
305 int type;
307 if (strncmp(printed_name, "&$", 2) == 0)
308 return;
310 if (is_socket_stuff(sm->sym))
311 return;
313 if (param >= 0) {
314 if (!slist_has_state(sm->possible, &user_data_set))
315 return;
316 type = USER_PTR_SET;
317 } else {
318 if (slist_has_state(sm->possible, &user_data_set))
319 type = USER_PTR_SET;
320 else if (slist_has_state(sm->possible, &user_data))
321 type = USER_PTR;
322 else
323 return;
325 if (parent_is_gone_var_sym(sm->name, sm->sym))
326 return;
328 sql_insert_return_states(return_id, return_ranges, type,
329 param, printed_name, "");
332 static void returns_user_ptr_helper(struct expression *expr, int param, char *key, char *value, bool set)
334 struct expression *arg;
335 struct expression *call;
336 char *name;
337 struct symbol *sym;
339 call = expr;
340 while (call->type == EXPR_ASSIGNMENT)
341 call = strip_expr(call->right);
342 if (call->type != EXPR_CALL)
343 return;
345 if (!set && !we_pass_user_data(call))
346 return;
348 if (param == -1) {
349 if (expr->type != EXPR_ASSIGNMENT) {
350 /* Nothing to do. Fake assignments should handle it */
351 return;
353 arg = expr->left;
354 goto set_user;
357 arg = get_argument_from_call_expr(call->args, param);
358 if (!arg)
359 return;
360 set_user:
361 name = get_variable_from_key(arg, key, &sym);
362 if (!name || !sym)
363 goto free;
364 if (param == -1 && strcmp(key, "$") == 0)
365 returns_user_data_fn = call;
366 if (set)
367 set_state(my_id, name, sym, &user_data_set);
368 else
369 set_state(my_id, name, sym, &user_data);
370 free:
371 free_string(name);
374 static void returns_user_ptr(struct expression *expr, int param, char *key, char *value)
376 returns_user_ptr_helper(expr, param, key, value, false);
379 static void returns_user_ptr_set(struct expression *expr, int param, char *key, char *value)
381 returns_user_ptr_helper(expr, param, key, value, true);
384 static void set_param_user_ptr(const char *name, struct symbol *sym, char *key, char *value)
386 struct expression *expr;
387 char *fullname;
389 expr = symbol_expression(sym);
390 fullname = get_variable_from_key(expr, key, NULL);
391 if (!fullname)
392 return;
393 set_state(my_id, fullname, sym, &user_data);
396 static void set_param_key_user_ptr_set(struct expression *expr, const char *name,
397 struct symbol *sym, void *data)
399 set_state(my_id, name, sym, &user_data_set);
402 static void set_param_key_user_ptr(struct expression *expr, const char *name,
403 struct symbol *sym, void *data)
405 set_state(my_id, name, sym, &user_data);
408 static void caller_info_callback(struct expression *call, int param, char *printed_name, struct sm_state *sm)
410 if (is_socket_stuff(sm->sym))
411 return;
413 if (!slist_has_state(sm->possible, &user_data) &&
414 !slist_has_state(sm->possible, &user_data_set))
415 return;
417 sql_insert_caller_info(call, USER_PTR, param, printed_name, "");
420 void register_points_to_user_data(int id)
422 struct user_fn_info *info;
423 int i;
425 my_id = id;
427 if (option_project != PROJ_KERNEL)
428 return;
430 add_function_data((unsigned long *)&returns_user_data_fn);
431 add_hook(&match_assign, ASSIGNMENT_HOOK);
433 add_function_hook("copy_from_user", &match_user_copy, NULL);
434 add_function_hook("memcpy_from_msg", &match_user_copy, NULL);
435 add_function_hook("__copy_from_user", &match_user_copy, NULL);
437 add_function_hook("memcpy", &match_memcpy, NULL);
438 add_function_hook("__memcpy", &match_memcpy, NULL);
440 add_caller_info_callback(my_id, caller_info_callback);
441 add_return_info_callback(my_id, return_info_callback);
443 select_caller_info_hook(set_param_user_ptr, USER_PTR);
444 select_return_states_hook(USER_PTR, &returns_user_ptr);
445 select_return_states_hook(USER_PTR_SET, &returns_user_ptr_set);
447 for (i = 0; i < ARRAY_SIZE(func_table); i++) {
448 info = &func_table[i];
449 add_function_param_key_hook_late(info->name, &set_param_key_user_ptr_set,
450 info->param, info->key, info);
453 for (i = 0; i < ARRAY_SIZE(call_table); i++) {
454 info = &call_table[i];
455 add_function_param_key_hook_early(info->name, &set_param_key_user_ptr,
456 info->param, info->key, info);