llvm: ensure SYM_NODE is stripped before accessing the return type
[smatch.git] / smatch_points_to_user_data.c
blobe96d91442a1af37cc32268875ec89833ee0fef79
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 // Old stuff that was here, but I no longer believe is user data
57 // kmap_atomic()
58 // skb_network_header
60 // add_function_hook("memcpy_fromiovec", &match_user_copy, INT_PTR(0));
61 // add_function_hook("usb_control_msg", &match_user_copy, INT_PTR(6));
63 static struct user_fn_info func_table[] = {
64 { "copy_from_user", USER_PTR_SET, 0, "$" },
65 { "__copy_from_user", USER_PTR_SET, 0, "$" },
66 { "kvm_read_guest_virt", USER_PTR_SET, 2, "$" },
67 { "vpu_iface_receive_msg", USER_PTR_SET, 1, "$" },
68 { "xdr_stream_decode_u32", USER_PTR_SET, 1, "$" },
70 { "(struct ksmbd_transport_ops)->read", USER_PTR_SET, 1, "$" },
71 { "nlmsg_data", USER_PTR_SET, -1, "$" },
72 { "nla_data", USER_PTR_SET, -1, "$" },
73 { "memdup_user", USER_PTR_SET, -1, "$" },
74 { "cfg80211_find_elem_match", USER_PTR_SET, -1, "$" },
75 { "ieee80211_bss_get_elem", USER_PTR_SET, -1, "$" },
76 { "cfg80211_find_elem", USER_PTR_SET, -1, "$" },
77 { "ieee80211_bss_get_ie", USER_PTR_SET, -1, "$" },
79 { "brcmf_fweh_dequeue_event", USER_PTR_SET, -1, "&$->emsg" },
80 { "wilc_wlan_rxq_remove", USER_PTR_SET, -1, "$->buffer" },
81 { "cfg80211_find_vendor_ie", USER_PTR_SET, -1, "$" },
83 { "xdr_copy_to_scratch", USER_PTR_SET, -1, "$" },
84 { "xdr_inline_decode", USER_PTR_SET, -1, "$" },
85 { "ntfs_read_run_nb", USER_PTR_SET, 3, "$" },
86 { "ntfs_read_bh", USER_PTR_SET, 3, "(0<~$0)" },
88 { "kstrtoull", USER_PTR_SET, 2, "$" },
89 { "kstrtoll", USER_PTR_SET, 2, "$" },
90 { "kstrtoul", USER_PTR_SET, 2, "$" },
91 { "kstrtol", USER_PTR_SET, 2, "$" },
92 { "kstrtoint", USER_PTR_SET, 2, "$" },
93 { "kstrtou64", USER_PTR_SET, 2, "$" },
94 { "kstrtos64", USER_PTR_SET, 2, "$" },
95 { "kstrtou32", USER_PTR_SET, 2, "$" },
96 { "kstrtos32", USER_PTR_SET, 2, "$" },
97 { "kstrtou16", USER_PTR_SET, 2, "$" },
98 { "kstrtos16", USER_PTR_SET, 2, "$" },
99 { "kstrtou8", USER_PTR_SET, 2, "$" },
100 { "kstrtos8", USER_PTR_SET, 2, "$" },
101 { "kstrtoull_from_user", USER_PTR_SET, 2, "$" },
102 { "kstrtoll_from_user", USER_PTR_SET, 2, "$" },
103 { "kstrtoul_from_user", USER_PTR_SET, 2, "$" },
104 { "kstrtol_from_user", USER_PTR_SET, 2, "$" },
105 { "kstrtouint_from_user", USER_PTR_SET, 2, "$" },
106 { "kstrtoint_from_user", USER_PTR_SET, 2, "$" },
107 { "kstrtou16_from_user", USER_PTR_SET, 2, "$" },
108 { "kstrtos16_from_user", USER_PTR_SET, 2, "$" },
109 { "kstrtou8_from_user", USER_PTR_SET, 2, "$" },
110 { "kstrtos8_from_user", USER_PTR_SET, 2, "$" },
111 { "kstrtou64_from_user", USER_PTR_SET, 2, "$" },
112 { "kstrtos64_from_user", USER_PTR_SET, 2, "$" },
113 { "kstrtou32_from_user", USER_PTR_SET, 2, "$" },
114 { "kstrtos32_from_user", USER_PTR_SET, 2, "$" },
117 static struct user_fn_info call_table[] = {
118 { "__handle_ksmbd_work", USER_DATA, 0, "$->request_buf" },
121 bool is_skb_data(struct expression *expr)
123 struct symbol *sym;
125 expr = strip_expr(expr);
126 if (!expr)
127 return false;
128 if (expr->type != EXPR_DEREF)
129 return false;
131 if (!expr->member)
132 return false;
133 if (strcmp(expr->member->name, "data") != 0)
134 return false;
136 sym = get_type(expr->deref);
137 if (!sym)
138 return false;
139 if (sym->type == SYM_PTR)
140 sym = get_real_base_type(sym);
141 if (!sym || sym->type != SYM_STRUCT || !sym->ident)
142 return false;
143 if (strcmp(sym->ident->name, "sk_buff") != 0)
144 return false;
146 return true;
149 static bool is_array_of_user_data(struct expression *expr)
151 struct expression *deref;
152 struct symbol *type;
154 if (expr->type == EXPR_PREOP && expr->op == '&') {
155 expr = strip_expr(expr->unop);
156 if (expr->type == EXPR_PREOP && expr->op == '*')
157 expr = strip_expr(expr->unop);
160 /* This is for array elements &foo->data[4] */
161 if (expr->type == EXPR_BINOP && expr->op == '+') {
162 if (points_to_user_data(expr->left))
163 return true;
164 if (points_to_user_data(expr->right))
165 return true;
168 /* This is for if you have: foo = skb->data; frob(foo->array); */
169 type = get_type(expr);
170 if (!type || type->type != SYM_ARRAY)
171 return false;
173 if (expr->type != EXPR_DEREF)
174 return false;
175 deref = strip_expr(expr->deref);
176 if (deref->type != EXPR_PREOP || deref->op != '*')
177 return false;
178 deref = strip_expr(deref->unop);
179 return points_to_user_data(deref);
182 static struct expression *remove_addr_stuff(struct expression *expr)
184 /* take "&foo->bar" and return "foo" */
185 expr = strip_expr(expr);
186 if (expr->type != EXPR_PREOP || expr->op != '&')
187 return expr;
188 expr = strip_expr(expr->unop);
189 while (expr && expr->type == EXPR_DEREF) {
190 expr = strip_expr(expr->deref);
191 if (expr->op == '.')
192 continue;
193 else
194 break;
196 if (expr->type == EXPR_PREOP && expr->op == '*')
197 expr = strip_expr(expr->unop);
198 return expr;
201 static bool math_points_to_user_data(struct expression *expr)
203 struct sm_state *sm;
205 // TODO: is_array_of_user_data() should probably be handled here
207 if (expr->type == EXPR_BINOP && expr->op == '+')
208 return math_points_to_user_data(expr->left);
210 expr = remove_addr_stuff(expr);
212 sm = get_sm_state_expr(my_id, expr);
213 if (!sm)
214 return false;
215 if (slist_has_state(sm->possible, &user_data) ||
216 slist_has_state(sm->possible, &user_data_set))
217 return true;
218 return false;
221 bool points_to_user_data(struct expression *expr)
223 struct sm_state *sm;
225 expr = strip_expr(expr);
226 if (!expr)
227 return false;
229 if (expr->type == EXPR_POSTOP)
230 expr = strip_expr(expr->unop);
232 if (is_fake_call(expr))
233 return false;
235 if (expr->type == EXPR_ASSIGNMENT)
236 return points_to_user_data(expr->left);
238 if (is_array_of_user_data(expr))
239 return true;
241 if (expr->type == EXPR_BINOP && expr->op == '+')
242 return math_points_to_user_data(expr);
244 if (is_skb_data(expr))
245 return true;
247 // FIXME if you have a struct pointer p then p->foo should be handled
248 // by smatch_kernel_user_data.c but if you have (p + 1)->foo then this
249 // should be handled here.
250 sm = get_sm_state_expr(my_id, expr);
251 if (!sm)
252 return false;
253 if (slist_has_state(sm->possible, &user_data) ||
254 slist_has_state(sm->possible, &user_data_set))
255 return true;
256 return false;
259 void set_array_user_ptr(struct expression *expr, bool is_new)
261 struct expression *tmp;
264 * If you have:
265 * p = buf;
266 * copy_from_user(p, src, 100);
267 * At the end, both "p" and "buf" point to user data.
270 tmp = get_assigned_expr(expr);
271 if (tmp)
272 set_state_expr(my_id, tmp, is_new ? &user_data_set : &user_data);
273 set_state_expr(my_id, expr, is_new ? &user_data_set : &user_data);
276 static void match_assign(struct expression *expr)
278 if (is_fake_call(expr->right))
279 return;
281 if (!is_ptr_type(get_type(expr->left)))
282 return;
284 if (points_to_user_data(expr->right)) {
285 // FIXME: if the types are different then mark the stuff on
286 // the left as user data.
287 set_state_expr(my_id, expr->left, &user_data);
288 return;
291 // FIXME: just use a modification hook
292 if (get_state_expr(my_id, expr->left))
293 set_state_expr(my_id, expr->left, &undefined);
296 static void match_memcpy(const char *fn, struct expression *expr, void *_unused)
298 struct expression *dest, *src;
300 dest = get_argument_from_call_expr(expr->args, 0);
301 src = get_argument_from_call_expr(expr->args, 1);
303 if (points_to_user_data(src)) {
304 set_state_expr(my_id, expr->left, &user_data_set);
305 return;
308 if (get_state_expr(my_id, dest))
309 set_state_expr(my_id, dest, &undefined);
312 static void fake_assign_helper(struct expression *expr, void *data)
314 struct expression *left = expr->left;
315 struct symbol *type;
316 bool set = data;
318 type = get_type(left);
319 if (!type)
320 return;
321 if (type->type == SYM_BASETYPE)
322 mark_as_user_data(left, set);
323 else if (type->type == SYM_ARRAY)
324 set_array_user_ptr(left, set);
327 static void returns_user_ptr_helper(struct expression *expr, const char *name, struct symbol *sym, bool set)
329 struct expression *call, *arg;
331 call = expr;
332 while (call && call->type == EXPR_ASSIGNMENT)
333 call = strip_expr(expr->right);
334 if (!call || call->type != EXPR_CALL)
335 return;
337 if (!set && !we_pass_user_data(call))
338 return;
340 arg = gen_expression_from_name_sym(name, sym);
341 if (!arg)
342 return;
344 create_recursive_fake_assignments(deref_expression(arg), &fake_assign_helper, INT_PTR(set));
346 if (arg->type == EXPR_PREOP && arg->op == '&') {
347 struct symbol *type;
349 type = get_type(arg->unop);
350 if (!type || type->type != SYM_ARRAY)
351 return;
354 set_state_expr(my_id, arg, set ? &user_data_set : &user_data);
357 static void returns_user_ptr(struct expression *expr, const char *name, struct symbol *sym, void *data)
359 returns_user_ptr_helper(expr, name, sym, false);
362 static void returns_user_ptr_set(struct expression *expr, const char *name, struct symbol *sym, void *data)
364 returns_user_ptr_helper(expr, name, sym, true);
367 static void set_param_user_ptr(const char *name, struct symbol *sym, char *value)
369 set_state(my_id, name, sym, &user_data);
372 static void set_caller_param_key_user_ptr(struct expression *expr, const char *name,
373 struct symbol *sym, void *data)
375 set_state(my_id, name, sym, &user_data);
378 static void caller_info_callback(struct expression *call, int param, char *printed_name, struct sm_state *sm)
380 if (is_socket_stuff(sm->sym))
381 return;
383 if (!slist_has_state(sm->possible, &user_data) &&
384 !slist_has_state(sm->possible, &user_data_set))
385 return;
387 sql_insert_caller_info(call, USER_PTR, param, printed_name, "");
390 static void return_info_callback(int return_id, char *return_ranges,
391 struct expression *returned_expr,
392 int param,
393 const char *printed_name,
394 struct sm_state *sm)
396 int type;
398 /* is this even possible? */
399 if (strcmp(printed_name, "&$") == 0)
400 return;
402 if (is_socket_stuff(sm->sym))
403 return;
405 if (param >= 0) {
406 if (!slist_has_state(sm->possible, &user_data_set))
407 return;
408 type = USER_PTR_SET;
409 } else {
410 if (slist_has_state(sm->possible, &user_data_set))
411 type = USER_PTR_SET;
412 else if (slist_has_state(sm->possible, &user_data))
413 type = USER_PTR;
414 else
415 return;
417 if (parent_is_gone_var_sym(sm->name, sm->sym))
418 return;
420 sql_insert_return_states(return_id, return_ranges, type,
421 param, printed_name, "");
424 void register_points_to_user_data(int id)
426 struct user_fn_info *info;
427 int i;
429 my_id = id;
431 if (option_project != PROJ_KERNEL)
432 return;
434 add_hook(&match_assign, ASSIGNMENT_HOOK);
436 add_function_hook("memcpy", &match_memcpy, NULL);
437 add_function_hook("__memcpy", &match_memcpy, NULL);
439 add_caller_info_callback(my_id, caller_info_callback);
440 add_return_info_callback(my_id, return_info_callback);
442 select_caller_name_sym(set_param_user_ptr, USER_PTR);
443 for (i = 0; i < ARRAY_SIZE(call_table); i++) {
444 info = &call_table[i];
445 add_function_param_key_hook_early(info->name,
446 &set_caller_param_key_user_ptr,
447 info->param, info->key, info);
450 select_return_param_key(USER_PTR, &returns_user_ptr);
451 select_return_param_key(USER_PTR_SET, &returns_user_ptr_set);
452 for (i = 0; i < ARRAY_SIZE(func_table); i++) {
453 info = &func_table[i];
454 add_function_param_key_hook_late(info->name,
455 &returns_user_ptr_set,
456 info->param, info->key, info);