#include <stdlib.h>
#include "parse.h"
#include "smatch.h"
#include "smatch_slist.h"
#include "smatch_extra.h"
struct limiter {
int buf_arg;
int limit_arg;
};
static struct limiter b0_l2 = {0, 2};
static struct limiter b1_l2 = {1, 2};
struct string_list *ignored_structs;
static int get_the_max(struct expression *expr, sval_t *sval)
{
struct range_list *rl;
if (get_hard_max(expr, sval))
return 1;
if (!option_spammy)
return 0;
if (get_fuzzy_max(expr, sval))
return 1;
if (!get_user_rl(expr, &rl))
return 0;
*sval = rl_max(rl);
return 1;
}
static int bytes_to_end_of_struct(struct expression *expr)
{
struct expression *deref;
struct symbol *type;
int struct_bytes;
int offset;
if (expr->type == EXPR_PREOP && expr->op == '&')
expr = strip_parens(expr->unop);
else {
type = get_type(expr);
if (!type || type->type != SYM_ARRAY)
return 0;
}
if (expr->type != EXPR_DEREF || !expr->member)
return 0;
deref = expr->deref;
if (deref->type == EXPR_PREOP && deref->op == '*')
deref = deref->unop;
struct_bytes = get_array_size_bytes_max(deref);
if (struct_bytes <= 0) {
type = get_type(expr->deref);
struct_bytes = type_bytes(type);
}
offset = get_member_offset_from_deref(expr);
if (offset <= 0)
return 0;
return struct_bytes - expr->member_offset;
}
static int size_of_union(struct expression *expr)
{
struct symbol *type;
if (expr->type != EXPR_PREOP || expr->op != '&')
return 0;
expr = strip_parens(expr->unop);
if (expr->type != EXPR_DEREF || !expr->member)
return 0;
expr = expr->unop;
type = get_type(expr);
if (!type || type->type != SYM_UNION)
return 0;
return type_bytes(type);
}
static int is_likely_multiple(int has, int needed, struct expression *limit)
{
sval_t mult;
limit = strip_parens(limit);
if (limit->type != EXPR_BINOP || limit->op != '*')
return 0;
if (!get_value(limit->left, &mult))
return 0;
if (has * mult.value == needed)
return 1;
if (!get_value(limit->right, &mult))
return 0;
if (has * mult.value == needed)
return 1;
return 0;
}
static int name_in_union(struct symbol *type, const char *name)
{
struct symbol *tmp;
if (type->type == SYM_NODE)
type = get_real_base_type(type);
if (!type || type->type != SYM_UNION)
return 0;
FOR_EACH_PTR(type->symbol_list, tmp) {
if (tmp->ident &&
strcmp(name, tmp->ident->name) == 0)
return 1;
} END_FOR_EACH_PTR(tmp);
return 0;
}
static int ends_on_struct_member_boundary(struct expression *expr, int needed)
{
struct symbol *type, *tmp;
int offset;
int size;
int found = 0;
expr = strip_expr(expr);
if (expr->type == EXPR_PREOP && expr->op == '&') {
expr = strip_parens(expr->unop);
} else {
type = get_type(expr);
if (!type || type->type != SYM_ARRAY)
return 0;
}
if (expr->type != EXPR_DEREF || !expr->member)
return 0;
type = get_type(expr->unop);
if (!type)
return 0;
if (type->type == SYM_UNION) {
struct expression *unop = strip_expr(expr->unop);
if (unop->type != EXPR_DEREF)
return 0;
type = get_type(unop->unop);
if (!type)
return 0;
}
if (type->type != SYM_STRUCT)
return 0;
offset = 0;
FOR_EACH_PTR(type->symbol_list, tmp) {
if (!found) {
if ((tmp->ident &&
strcmp(expr->member->name, tmp->ident->name) == 0) ||
name_in_union(tmp, expr->member->name))
found = 1;
offset = ALIGN(offset, tmp->ctype.alignment);
offset += type_bytes(tmp);
size = type_bytes(tmp);
continue;
}
if (offset != ALIGN(offset, tmp->ctype.alignment))
return 0;
offset += type_bytes(tmp);
size += type_bytes(tmp);
if (size == needed)
return 1;
if (size > needed)
return 0;
} END_FOR_EACH_PTR(tmp);
return 0;
}
static int is_one_element_array(struct expression *expr)
{
struct symbol *type;
sval_t sval;
if (expr->type == EXPR_PREOP && expr->op == '&')
expr = expr->unop;
if (expr->type == EXPR_BINOP)
return 0;
type = get_type(expr);
if (!type)
return 0;
if (!type || type->type != SYM_ARRAY)
return 0;
if (!get_implied_value(type->array_size, &sval))
return 0;
if (sval.value == 1)
return 1;
return 0;
}
static int is_ignored_struct(struct expression *expr)
{
struct symbol *type;
type = get_type(expr);
if (!type)
return 0;
if (type->type == SYM_PTR)
type = get_real_base_type(type);
if (type->type != SYM_STRUCT)
return 0;
if (!type->ident)
return 0;
if (list_has_string(ignored_structs, type->ident->name))
return 1;
return 0;
}
static void match_limited(const char *fn, struct expression *expr, void *_limiter)
{
struct limiter *limiter = (struct limiter *)_limiter;
struct expression *dest;
struct expression *limit;
char *dest_name = NULL;
sval_t needed;
int has;
dest = get_argument_from_call_expr(expr->args, limiter->buf_arg);
limit = get_argument_from_call_expr(expr->args, limiter->limit_arg);
if (!get_the_max(limit, &needed))
return;
has = get_array_size_bytes_max(dest);
if (!has)
return;
if (has >= needed.value)
return;
if (needed.value == bytes_to_end_of_struct(dest))
return;
if (needed.value <= size_of_union(dest))
return;
if (is_likely_multiple(has, needed.value, limit))
return;
if (ends_on_struct_member_boundary(dest, needed.value))
return;
if (is_one_element_array(dest))
return;
if (is_ignored_struct(dest))
return;
dest_name = expr_to_str(dest);
sm_error("%s() '%s' too small (%d vs %s)", fn, dest_name, has, sval_to_str(needed));
free_string(dest_name);
}
static void register_funcs_from_file(void)
{
char name[256];
struct token *token;
const char *func;
int size, buf;
struct limiter *limiter;
snprintf(name, 256, "%s.sizeof_param", option_project_str);
name[255] = '\0';
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)
break;
func = show_ident(token->ident);
token = token->next;
if (token_type(token) != TOKEN_NUMBER)
break;
size = atoi(token->number);
token = token->next;
if (token_type(token) == TOKEN_SPECIAL) {
if (token->special != '-')
break;
token = token->next;
if (token_type(token) != TOKEN_NUMBER)
break;
token = token->next;
continue;
}
if (token_type(token) != TOKEN_NUMBER)
break;
buf = atoi(token->number);
limiter = malloc(sizeof(*limiter));
limiter->limit_arg = size;
limiter->buf_arg = buf;
add_function_hook(func, &match_limited, limiter);
token = token->next;
}
if (token_type(token) != TOKEN_STREAMEND)
sm_perror("parsing '%s'", name);
clear_token_alloc();
}
static void register_ignored_structs_from_file(void)
{
char name[256];
struct token *token;
const char *struct_type;
snprintf(name, 256, "%s.ignore_memcpy_struct_overflows", option_project_str);
name[255] = '\0';
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;
struct_type = show_ident(token->ident);
insert_string(&ignored_structs, alloc_string(struct_type));
token = token->next;
}
clear_token_alloc();
}
void check_memcpy_overflow(int id)
{
register_funcs_from_file();
register_ignored_structs_from_file();
add_function_hook("memcmp", &match_limited, &b0_l2);
add_function_hook("memcmp", &match_limited, &b1_l2);
if (option_project == PROJ_KERNEL) {
add_function_hook("copy_to_user", &match_limited, &b1_l2);
add_function_hook("_copy_to_user", &match_limited, &b1_l2);
add_function_hook("__copy_to_user", &match_limited, &b1_l2);
add_function_hook("copy_from_user", &match_limited, &b0_l2);
add_function_hook("_copy_from_user", &match_limited, &b0_l2);
add_function_hook("__copy_from_user", &match_limited, &b0_l2);
}
}