From 45df3fee7ce0de02c514791821a1d004282bcd8f Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Wed, 17 Apr 2013 11:42:54 +0300 Subject: [PATCH] *new* smatch_clear_buffer.c: handle memset() type functions The smatch_param_cleared.c file records which functions clear their parameters in the database. The smatch_clear_buffer.c modifies smatch_extra.c and provides some helper functions to query if all the struct members are cleared. Signed-off-by: Dan Carpenter --- Makefile | 3 +- smatch.h | 5 + smatch_clear_buffer.c | 308 +++++++++++++++++++++++++++++++++++++++++++++++++ smatch_param_cleared.c | 155 +++++++++++++++++++++++++ 4 files changed, 470 insertions(+), 1 deletion(-) create mode 100644 smatch_clear_buffer.c create mode 100644 smatch_param_cleared.c diff --git a/Makefile b/Makefile index 81925833..577f3647 100644 --- a/Makefile +++ b/Makefile @@ -51,7 +51,8 @@ SMATCH_FILES=smatch_flow.o smatch_conditions.o smatch_slist.o smatch_states.o \ smatch_expressions.o smatch_returns.o smatch_parse_call_math.o \ smatch_param_limit.o smatch_param_filter.o \ smatch_param_set.o smatch_comparison.o smatch_local_values.o \ - smatch_function_ptrs.o smatch_annotate.o smatch_string_list.o + smatch_function_ptrs.o smatch_annotate.o smatch_string_list.o \ + smatch_param_cleared.o smatch_clear_buffer.o SMATCH_CHECKS=$(shell ls check_*.c | sed -e 's/\.c/.o/') SMATCH_DATA=smatch_data/kernel.allocation_funcs smatch_data/kernel.balanced_funcs \ diff --git a/smatch.h b/smatch.h index d60b58a2..bb69c67d 100644 --- a/smatch.h +++ b/smatch.h @@ -497,6 +497,7 @@ enum info_type { LIMITED_VALUE = 11, ADDED_VALUE = 12, FILTER_VALUE = 13, + PARAM_CLEARED = 14, }; void add_definition_db_callback(void (*callback)(const char *name, struct symbol *sym, char *key, char *value), int type); @@ -636,4 +637,8 @@ void insert_string(struct string_list **str_list, char *str); struct string_list *clone_str_list(struct string_list *orig); struct string_list *combine_string_lists(struct string_list *one, struct string_list *two); +/* smatch_clear_buffer.c */ +int is_uninitialized(struct expression *expr); +int has_uninitialized_members(struct expression *expr); + #endif /* !SMATCH_H_ */ diff --git a/smatch_clear_buffer.c b/smatch_clear_buffer.c new file mode 100644 index 00000000..bf16c1f8 --- /dev/null +++ b/smatch_clear_buffer.c @@ -0,0 +1,308 @@ +/* + * smatch/smatch_clear_structs.c + * + * Copyright (C) 2013 Oracle. + * + * Licensed under the Open Software License version 1.1 + * + */ + +#include "smatch.h" +#include "smatch_slist.h" +#include "smatch_extra.h" + +static int my_id; + +STATE(uninitialized); +STATE(initialized); + +static int type_sym_array(struct expression *expr) +{ + struct symbol *type; + + /* remove casting the array to a pointer */ + expr = strip_expr(expr); + type = get_type(expr); + if (type && type->type == SYM_ARRAY) + return 1; + return 0; +} + +static void set_members_uninitialized(struct expression *expr) +{ + struct symbol *type, *sym, *tmp; + char *name; + char buf[256]; + + type = get_type(expr); + if (!type) + return; + + if (type->type != SYM_PTR) + return; + type = get_real_base_type(type); + + name = expr_to_var_sym(expr, &sym); + if (!name || !sym) + goto free; + + FOR_EACH_PTR(type->symbol_list, tmp) { + if (!tmp->ident) + continue; + snprintf(buf, sizeof(buf), "%s->%s", name, tmp->ident->name); + set_state(my_id, buf, sym, &uninitialized); + } END_FOR_EACH_PTR(tmp); + +free: + free_string(name); +} + +static void initialize_struct_members(struct symbol *type, struct expression *expr, struct expression *to) +{ + struct symbol *tmp; + struct expression *member; + struct expression *assign; + int op = '*'; + + if (expr->type == EXPR_PREOP && expr->op == '&') { + expr = strip_expr(expr->unop); + op = '.'; + } + + FOR_EACH_PTR(type->symbol_list, tmp) { + if (!tmp->ident) + continue; + member = member_expression(expr, op, tmp->ident); + if (type_sym_array(member)) + continue; + + /* + * FIXME: the problem here is that sometimes we have: + * memset(&my_struct, 0xff, sizeof(my_struct)); + * and my_struct->foo is a 1 bit bitfield. There is a check + * which complains that "255 doesn't fit in ->foo". + * + * For now I just have this ugly hack. But really it's not + * handling the memset(..., 0xff, ...) correctly at all so one + * more hack barely makes a difference. + * + */ + if (to && type_positive_bits(get_type(member)) >= type_positive_bits(get_type(to))) + assign = assign_expression(member, to); + else + assign = assign_expression(member, member); + + __split_expr(assign); + } END_FOR_EACH_PTR(tmp); +} + +static void initialize_base_type(struct symbol *type, struct expression *expr, + struct expression *to) +{ + struct expression *assign; + + if (type == &void_ctype) + return; + if (expr->type == EXPR_PREOP && expr->op == '&') + expr = strip_expr(expr->unop); + else + expr = deref_expression(expr); + if (!to) + to = expr; + /* FIXME: see the FIXME in initialize_struct_members() */ + if (type_positive_bits(get_type(expr)) < type_positive_bits(get_type(to))) + to = expr; + assign = assign_expression(expr, to); + __split_expr(assign); +} + +void set_initialized(struct expression *expr, struct expression *to) +{ + struct symbol *type; + + expr = strip_expr(expr); + + type = get_type(expr); + if (!type || type->type != SYM_PTR) + return; + type = get_real_base_type(type); + if (!type) + return; + if (type->type == SYM_BASETYPE) + initialize_base_type(type, expr, to); + if (type->type == SYM_STRUCT) { + if (expr->type != EXPR_PREOP || expr->op != '&') + expr = deref_expression(expr); + initialize_struct_members(type, expr, to); + } +} + +int is_uninitialized(struct expression *expr) +{ + struct sm_state *sm; + + sm = get_sm_state_expr(my_id, expr); + if (!sm) + return 0; + return slist_has_state(sm->possible, &uninitialized); +} + +int has_uninitialized_members(struct expression *expr) +{ + struct symbol *sym; + struct symbol *tmp; + char *name; + char buf[256]; + struct sm_state *sm; + + sym = get_type(expr); + if (!sym) + return 0; + + if (sym->type == SYM_PTR) + sym = get_real_base_type(sym); + + name = expr_to_var(expr); + if (!name) + return 0; + + FOR_EACH_PTR(sym->symbol_list, tmp) { + if (!tmp->ident) + continue; + snprintf(buf, sizeof(buf), "%s->%s", name, tmp->ident->name); + sm = get_sm_state(my_id, buf, sym); + if (!sm) + continue; + if (slist_has_state(sm->possible, &uninitialized)) + return 1; + } END_FOR_EACH_PTR(tmp); + + free_string(name); + return 0; +} + +static void match_assign(struct expression *expr) +{ + struct symbol *type; + + type = get_type(expr->left); + if (!type || type->type != SYM_STRUCT) + return; + initialize_struct_members(type, expr->left, NULL); +} + +static void match_alloc(const char *fn, struct expression *expr, void *_size_arg) +{ + set_members_uninitialized(expr->left); +} + +static void db_param_cleared(struct expression *expr, int param, char *key, char *value) +{ + struct expression *arg; + + while (expr->type == EXPR_ASSIGNMENT) + expr = strip_expr(expr->right); + if (expr->type != EXPR_CALL) + return; + + arg = get_argument_from_call_expr(expr->args, param); + if (!arg) + return; + + if (strcmp(value, "0") == 0) + set_initialized(arg, zero_expr()); + else + set_initialized(arg, NULL); +} + +static void match_memset(const char *fn, struct expression *expr, void *_size_arg) +{ + struct expression *buf; + struct expression *val; + + buf = get_argument_from_call_expr(expr->args, 0); + val = get_argument_from_call_expr(expr->args, 1); + + set_initialized(buf, val); +} + +static void match_memcpy(const char *fn, struct expression *expr, void *_arg) +{ + struct expression *buf; + int arg = PTR_INT(_arg); + + buf = get_argument_from_call_expr(expr->args, arg); + + set_initialized(buf, NULL); +} + +static void reset_initialized(struct sm_state *sm) +{ + set_state(my_id, sm->name, sm->sym, &initialized); +} + +#define USB_DIR_IN 0x80 +static void match_usb_control_msg(const char *fn, struct expression *expr, void *_size_arg) +{ + struct expression *inout; + struct expression *buf; + sval_t sval; + + inout = get_argument_from_call_expr(expr->args, 3); + buf = get_argument_from_call_expr(expr->args, 6); + + if (get_value(inout, &sval) && !(sval.uvalue & USB_DIR_IN)) + return; + + set_initialized(buf, NULL); +} + +static void register_clears_param(void) +{ + struct token *token; + char name[256]; + const char *function; + int param; + + if (option_project == PROJ_NONE) + return; + + snprintf(name, 256, "%s.clears_argument", option_project_str); + + token = get_tokens_file(name); + if (!token) + return; + if (token_type(token) != TOKEN_STREAMBEGIN) + return; + token = token->next; + while (token_type(token) != TOKEN_STREAMEND) { + if (token_type(token) != TOKEN_IDENT) + return; + function = show_ident(token->ident); + token = token->next; + if (token_type(token) != TOKEN_NUMBER) + return; + param = atoi(token->number); + add_function_hook(function, &match_memcpy, INT_PTR(param)); + token = token->next; + } + clear_token_alloc(); +} + +void register_clear_buffer(int id) +{ + my_id = id; + if (option_project == PROJ_KERNEL) { + add_function_assign_hook("kmalloc", &match_alloc, NULL); + add_function_hook("usb_control_msg", &match_usb_control_msg, NULL); + } + add_modification_hook(my_id, &reset_initialized); + add_hook(&match_assign, ASSIGNMENT_HOOK); + add_function_hook("memset", &match_memset, NULL); + add_function_hook("memcpy", &match_memcpy, INT_PTR(0)); + add_function_hook("memmove", &match_memcpy, INT_PTR(0)); + register_clears_param(); + + add_db_return_states_callback(PARAM_CLEARED, &db_param_cleared); +} + diff --git a/smatch_param_cleared.c b/smatch_param_cleared.c new file mode 100644 index 00000000..b0571bd6 --- /dev/null +++ b/smatch_param_cleared.c @@ -0,0 +1,155 @@ +/* + * sparse/smatch_param_cleared.c + * + * Copyright (C) 2012 Oracle. + * + * Licensed under the Open Software License version 1.1 + * + */ + +/* + * This works together with smatch_clear_buffer.c. This one is only for + * tracking the information and smatch_clear_buffer.c changes SMATCH_EXTRA. + * + * This tracks functions like memset() which clear out a chunk of memory. + * It fills in a gap that smatch_param_set.c can't handle. It only handles + * void pointers because smatch_param_set.c should handle the rest. + */ + +#include "scope.h" +#include "smatch.h" +#include "smatch_slist.h" +#include "smatch_extra.h" + +static int my_id; + +STATE(cleared); +STATE(zeroed); + +static void db_param_cleared(struct expression *expr, int param, char *key, char *value) +{ + struct expression *arg; + + while (expr->type == EXPR_ASSIGNMENT) + expr = strip_expr(expr->right); + if (expr->type != EXPR_CALL) + return; + + arg = get_argument_from_call_expr(expr->args, param); + arg = strip_expr(arg); + if (!arg) + return; + if (arg->type != EXPR_SYMBOL) + return; + if (get_param_num_from_sym(arg->symbol) < 0) + return; + + if (strcmp(value, "0") == 0) + set_state_expr(my_id, arg, &zeroed); + else + set_state_expr(my_id, arg, &cleared); +} + +static void match_memset(const char *fn, struct expression *expr, void *arg) +{ + db_param_cleared(expr, PTR_INT(arg), (char *)"$$", (char *)"0"); +} + +static void match_memcpy(const char *fn, struct expression *expr, void *arg) +{ + db_param_cleared(expr, PTR_INT(arg), (char *)"$$", (char *)""); +} + +static void print_return_value_param(int return_id, char *return_ranges, struct expression *expr, struct state_list *slist) +{ + struct state_list *my_slist; + struct sm_state *sm; + int param; + + my_slist = get_all_states_slist(my_id, slist); + + FOR_EACH_PTR(my_slist, sm) { + param = get_param_num_from_sym(sm->sym); + if (param < 0) + continue; + + if (sm->state == &zeroed) { + sql_insert_return_states(return_id, return_ranges, + PARAM_CLEARED, param, "$$", "0"); + } + + if (sm->state == &cleared) { + sql_insert_return_states(return_id, return_ranges, + PARAM_CLEARED, param, "$$", ""); + } + } END_FOR_EACH_PTR(sm); +} + +static void register_clears_param(void) +{ + struct token *token; + char name[256]; + const char *function; + int param; + + if (option_project == PROJ_NONE) + return; + + snprintf(name, 256, "%s.clears_argument", option_project_str); + + token = get_tokens_file(name); + if (!token) + return; + if (token_type(token) != TOKEN_STREAMBEGIN) + return; + token = token->next; + while (token_type(token) != TOKEN_STREAMEND) { + if (token_type(token) != TOKEN_IDENT) + return; + function = show_ident(token->ident); + token = token->next; + if (token_type(token) != TOKEN_NUMBER) + return; + param = atoi(token->number); + add_function_hook(function, &match_memcpy, INT_PTR(param)); + token = token->next; + } + clear_token_alloc(); +} + +#define USB_DIR_IN 0x80 +static void match_usb_control_msg(const char *fn, struct expression *expr, void *_size_arg) +{ + struct expression *inout; + sval_t sval; + + inout = get_argument_from_call_expr(expr->args, 3); + + if (get_value(inout, &sval) && !(sval.uvalue & USB_DIR_IN)) + return; + + db_param_cleared(expr, 6, (char *)"$$", (char *)""); +} + +void register_param_cleared(int id) +{ + if (!option_info) + return; + + my_id = id; + + add_function_hook("memset", &match_memset, INT_PTR(0)); + + add_function_hook("memcpy", &match_memcpy, INT_PTR(0)); + add_function_hook("memmove", &match_memcpy, INT_PTR(0)); + register_clears_param(); + + add_db_return_states_callback(PARAM_CLEARED, &db_param_cleared); + add_returned_state_callback(&print_return_value_param); + + if (option_project == PROJ_KERNEL) { + add_function_hook("usb_control_msg", &match_usb_control_msg, NULL); + } + +} + -- 2.11.4.GIT