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:
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
28 * So now we've separated out the stuff that points to a user_buf from the other
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.
42 #include "smatch_slist.h"
43 #include "smatch_extra.h"
56 // Old stuff that was here, but I no longer believe is user data
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)" },
87 { "__wbuf", USER_PTR_SET
, 1, "*$" },
89 { "kstrtoull", USER_PTR_SET
, 2, "$" },
90 { "kstrtoll", USER_PTR_SET
, 2, "$" },
91 { "kstrtoul", USER_PTR_SET
, 2, "$" },
92 { "kstrtol", USER_PTR_SET
, 2, "$" },
93 { "kstrtoint", USER_PTR_SET
, 2, "$" },
94 { "kstrtou64", USER_PTR_SET
, 2, "$" },
95 { "kstrtos64", USER_PTR_SET
, 2, "$" },
96 { "kstrtou32", USER_PTR_SET
, 2, "$" },
97 { "kstrtos32", USER_PTR_SET
, 2, "$" },
98 { "kstrtou16", USER_PTR_SET
, 2, "$" },
99 { "kstrtos16", USER_PTR_SET
, 2, "$" },
100 { "kstrtou8", USER_PTR_SET
, 2, "$" },
101 { "kstrtos8", USER_PTR_SET
, 2, "$" },
102 { "kstrtoull_from_user", USER_PTR_SET
, 2, "$" },
103 { "kstrtoll_from_user", USER_PTR_SET
, 2, "$" },
104 { "kstrtoul_from_user", USER_PTR_SET
, 2, "$" },
105 { "kstrtol_from_user", USER_PTR_SET
, 2, "$" },
106 { "kstrtouint_from_user", USER_PTR_SET
, 2, "$" },
107 { "kstrtoint_from_user", USER_PTR_SET
, 2, "$" },
108 { "kstrtou16_from_user", USER_PTR_SET
, 2, "$" },
109 { "kstrtos16_from_user", USER_PTR_SET
, 2, "$" },
110 { "kstrtou8_from_user", USER_PTR_SET
, 2, "$" },
111 { "kstrtos8_from_user", USER_PTR_SET
, 2, "$" },
112 { "kstrtou64_from_user", USER_PTR_SET
, 2, "$" },
113 { "kstrtos64_from_user", USER_PTR_SET
, 2, "$" },
114 { "kstrtou32_from_user", USER_PTR_SET
, 2, "$" },
115 { "kstrtos32_from_user", USER_PTR_SET
, 2, "$" },
118 static struct user_fn_info call_table
[] = {
119 { "__handle_ksmbd_work", USER_DATA
, 0, "$->request_buf" },
122 bool is_skb_data(struct expression
*expr
)
126 expr
= strip_expr(expr
);
129 if (expr
->type
!= EXPR_DEREF
)
134 if (strcmp(expr
->member
->name
, "data") != 0)
137 sym
= get_type(expr
->deref
);
140 if (sym
->type
== SYM_PTR
)
141 sym
= get_real_base_type(sym
);
142 if (!sym
|| sym
->type
!= SYM_STRUCT
|| !sym
->ident
)
144 if (strcmp(sym
->ident
->name
, "sk_buff") != 0)
150 static bool is_array_of_user_data(struct expression
*expr
)
152 struct expression
*deref
;
155 if (expr
->type
== EXPR_PREOP
&& expr
->op
== '&') {
156 expr
= strip_expr(expr
->unop
);
157 if (expr
->type
== EXPR_PREOP
&& expr
->op
== '*')
158 expr
= strip_expr(expr
->unop
);
161 /* This is for array elements &foo->data[4] */
162 if (expr
->type
== EXPR_BINOP
&& expr
->op
== '+') {
163 if (points_to_user_data(expr
->left
))
165 if (points_to_user_data(expr
->right
))
169 /* This is for if you have: foo = skb->data; frob(foo->array); */
170 type
= get_type(expr
);
171 if (!type
|| type
->type
!= SYM_ARRAY
)
174 if (expr
->type
!= EXPR_DEREF
)
176 deref
= strip_expr(expr
->deref
);
177 if (deref
->type
!= EXPR_PREOP
|| deref
->op
!= '*')
179 deref
= strip_expr(deref
->unop
);
180 return points_to_user_data(deref
);
183 static struct expression
*remove_addr_stuff(struct expression
*expr
)
185 /* take "&foo->bar" and return "foo" */
186 expr
= strip_expr(expr
);
187 if (expr
->type
!= EXPR_PREOP
|| expr
->op
!= '&')
189 expr
= strip_expr(expr
->unop
);
190 while (expr
&& expr
->type
== EXPR_DEREF
) {
191 expr
= strip_expr(expr
->deref
);
197 if (expr
->type
== EXPR_PREOP
&& expr
->op
== '*')
198 expr
= strip_expr(expr
->unop
);
202 static bool math_points_to_user_data(struct expression
*expr
)
206 // TODO: is_array_of_user_data() should probably be handled here
208 if (expr
->type
== EXPR_BINOP
&& expr
->op
== '+')
209 return math_points_to_user_data(expr
->left
);
211 expr
= remove_addr_stuff(expr
);
213 sm
= get_sm_state_expr(my_id
, expr
);
216 if (slist_has_state(sm
->possible
, &user_data
) ||
217 slist_has_state(sm
->possible
, &user_data_set
))
222 bool points_to_user_data(struct expression
*expr
)
226 expr
= strip_expr(expr
);
230 if (expr
->type
== EXPR_POSTOP
)
231 expr
= strip_expr(expr
->unop
);
233 if (is_fake_call(expr
))
236 if (expr
->type
== EXPR_ASSIGNMENT
)
237 return points_to_user_data(expr
->left
);
239 if (is_array_of_user_data(expr
))
242 if (expr
->type
== EXPR_BINOP
&& expr
->op
== '+')
243 return math_points_to_user_data(expr
);
245 if (is_skb_data(expr
))
248 // FIXME if you have a struct pointer p then p->foo should be handled
249 // by smatch_kernel_user_data.c but if you have (p + 1)->foo then this
250 // should be handled here.
251 sm
= get_sm_state_expr(my_id
, expr
);
254 if (slist_has_state(sm
->possible
, &user_data
) ||
255 slist_has_state(sm
->possible
, &user_data_set
))
260 void set_array_user_ptr(struct expression
*expr
, bool is_new
)
262 struct expression
*tmp
;
267 * copy_from_user(p, src, 100);
268 * At the end, both "p" and "buf" point to user data.
271 tmp
= get_assigned_expr(expr
);
273 set_state_expr(my_id
, tmp
, is_new
? &user_data_set
: &user_data
);
274 set_state_expr(my_id
, expr
, is_new
? &user_data_set
: &user_data
);
277 static void match_assign(struct expression
*expr
)
279 if (is_fake_call(expr
->right
))
282 if (!is_ptr_type(get_type(expr
->left
)))
285 if (points_to_user_data(expr
->right
)) {
286 // FIXME: if the types are different then mark the stuff on
287 // the left as user data.
288 set_state_expr(my_id
, expr
->left
, &user_data
);
292 // FIXME: just use a modification hook
293 if (get_state_expr(my_id
, expr
->left
))
294 set_state_expr(my_id
, expr
->left
, &undefined
);
297 static void match_memcpy(const char *fn
, struct expression
*expr
, void *_unused
)
299 struct expression
*dest
, *src
;
301 dest
= get_argument_from_call_expr(expr
->args
, 0);
302 src
= get_argument_from_call_expr(expr
->args
, 1);
304 if (points_to_user_data(src
)) {
305 set_state_expr(my_id
, expr
->left
, &user_data_set
);
309 if (get_state_expr(my_id
, dest
))
310 set_state_expr(my_id
, dest
, &undefined
);
313 static void fake_assign_helper(struct expression
*expr
, void *data
)
315 struct expression
*left
= expr
->left
;
319 type
= get_type(left
);
322 if (type
->type
== SYM_BASETYPE
)
323 mark_as_user_data(left
, set
);
324 else if (type
->type
== SYM_ARRAY
)
325 set_array_user_ptr(left
, set
);
328 static void returns_user_ptr_helper(struct expression
*expr
, const char *name
, struct symbol
*sym
, bool set
)
330 struct expression
*call
, *arg
;
333 while (call
&& call
->type
== EXPR_ASSIGNMENT
)
334 call
= strip_expr(expr
->right
);
335 if (!call
|| call
->type
!= EXPR_CALL
)
338 if (!set
&& !we_pass_user_data(call
))
341 arg
= gen_expression_from_name_sym(name
, sym
);
345 create_recursive_fake_assignments(deref_expression(arg
), &fake_assign_helper
, INT_PTR(set
));
347 if (arg
->type
== EXPR_PREOP
&& arg
->op
== '&') {
350 type
= get_type(arg
->unop
);
351 if (!type
|| type
->type
!= SYM_ARRAY
)
355 set_state_expr(my_id
, arg
, set
? &user_data_set
: &user_data
);
358 static void returns_user_ptr(struct expression
*expr
, const char *name
, struct symbol
*sym
, void *data
)
360 returns_user_ptr_helper(expr
, name
, sym
, false);
363 static void returns_user_ptr_set(struct expression
*expr
, const char *name
, struct symbol
*sym
, void *data
)
365 returns_user_ptr_helper(expr
, name
, sym
, true);
368 static void set_param_user_ptr(const char *name
, struct symbol
*sym
, char *value
)
370 set_state(my_id
, name
, sym
, &user_data
);
373 static void set_caller_param_key_user_ptr(struct expression
*expr
, const char *name
,
374 struct symbol
*sym
, void *data
)
376 set_state(my_id
, name
, sym
, &user_data
);
379 static void caller_info_callback(struct expression
*call
, int param
, char *printed_name
, struct sm_state
*sm
)
381 if (is_socket_stuff(sm
->sym
))
384 if (!slist_has_state(sm
->possible
, &user_data
) &&
385 !slist_has_state(sm
->possible
, &user_data_set
))
388 sql_insert_caller_info(call
, USER_PTR
, param
, printed_name
, "");
391 static void return_info_callback(int return_id
, char *return_ranges
,
392 struct expression
*returned_expr
,
394 const char *printed_name
,
399 /* is this even possible? */
400 if (strcmp(printed_name
, "&$") == 0)
403 if (is_socket_stuff(sm
->sym
))
407 if (!slist_has_state(sm
->possible
, &user_data_set
))
411 if (slist_has_state(sm
->possible
, &user_data_set
))
413 else if (slist_has_state(sm
->possible
, &user_data
))
418 if (parent_is_gone_var_sym(sm
->name
, sm
->sym
))
421 sql_insert_return_states(return_id
, return_ranges
, type
,
422 param
, printed_name
, "");
425 void register_points_to_user_data(int id
)
427 struct user_fn_info
*info
;
432 if (option_project
!= PROJ_KERNEL
)
435 add_hook(&match_assign
, ASSIGNMENT_HOOK
);
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_name_sym(set_param_user_ptr
, USER_PTR
);
444 for (i
= 0; i
< ARRAY_SIZE(call_table
); i
++) {
445 info
= &call_table
[i
];
446 add_function_param_key_hook_early(info
->name
,
447 &set_caller_param_key_user_ptr
,
448 info
->param
, info
->key
, info
);
451 select_return_param_key(USER_PTR
, &returns_user_ptr
);
452 select_return_param_key(USER_PTR_SET
, &returns_user_ptr_set
);
453 for (i
= 0; i
< ARRAY_SIZE(func_table
); i
++) {
454 info
= &func_table
[i
];
455 add_function_param_key_hook_late(info
->name
,
456 &returns_user_ptr_set
,
457 info
->param
, info
->key
, info
);