RAP-optimizations/src/size_overflow_plugin/size_overflow_transform.c
2018-08-20 04:10:13 +08:00

773 lines
20 KiB
C

/*
* Copyright 2011-2017 by Emese Revfy <re.emese@gmail.com>
* Licensed under the GPL v2
*
* Homepage:
* https://github.com/ephox-gcc-plugins/size_overflow
*
* Documentation:
* http://forums.grsecurity.net/viewtopic.php?f=7&t=3043
*
* This plugin recomputes expressions of function arguments marked by a size_overflow attribute
* with double integer precision (DImode/TImode for 32/64 bit integer types).
* The recomputed argument is checked against TYPE_MAX and an event is logged on overflow and the triggering process is killed.
*
* Usage:
* $ make
* $ make run
*/
#include "size_overflow.h"
static tree cast_to_orig_type(struct visited *visited, gimple stmt, const_tree orig_node, tree new_node)
{
gimple def_stmt;
const_gimple assign;
tree result, orig_type = TREE_TYPE(orig_node);
gimple_stmt_iterator gsi;
if (gimple_code(stmt) != GIMPLE_PHI) {
gsi = gsi_for_stmt(stmt);
assign = build_cast_stmt(visited, orig_type, new_node, CREATE_NEW_VAR, &gsi, BEFORE_STMT, false);
return get_lhs(assign);
}
def_stmt = get_def_stmt(new_node);
if (gimple_code(def_stmt) == GIMPLE_PHI)
gsi = gsi_after_labels(gimple_bb(def_stmt));
else
gsi = gsi_for_stmt(def_stmt);
result = gimple_phi_result(stmt);
assign = build_cast_stmt(visited, orig_type, new_node, SSA_NAME_VAR(result), &gsi, AFTER_STMT, false);
return get_lhs(assign);
}
static void change_size_overflow_asm_input(gasm *stmt, tree new_input)
{
tree list;
gcc_assert(is_size_overflow_insert_check_asm(stmt));
list = build_tree_list(NULL_TREE, build_const_char_string(3, "rm"));
list = chainon(NULL_TREE, build_tree_list(list, new_input));
gimple_asm_set_input_op(stmt, 0, list);
}
static void change_field_write_rhs(gassign *assign, const_tree orig_rhs, tree new_rhs)
{
const_tree rhs1, rhs2, rhs3 = NULL_TREE;
rhs1 = gimple_assign_rhs1(assign);
if (rhs1 == orig_rhs) {
gimple_assign_set_rhs1(assign, new_rhs);
return;
}
rhs2 = gimple_assign_rhs2(assign);
if (rhs2 == orig_rhs) {
gimple_assign_set_rhs2(assign, new_rhs);
return;
}
#if BUILDING_GCC_VERSION >= 4006
rhs3 = gimple_assign_rhs3(assign);
if (rhs3 == orig_rhs) {
gimple_assign_set_rhs3(assign, new_rhs);
return;
}
#endif
debug_gimple_stmt(assign);
fprintf(stderr, "orig_rhs:\n");
debug_tree(orig_rhs);
fprintf(stderr, "rhs1:\n");
debug_tree(rhs1);
fprintf(stderr, "rhs2:\n");
debug_tree(rhs2);
fprintf(stderr, "rhs3:\n");
debug_tree(rhs3);
gcc_unreachable();
}
static void change_phi_arg(gphi *phi, tree new_node, unsigned int num)
{
unsigned int i;
location_t loc = gimple_location(phi);
for (i = 0; i < gimple_phi_num_args(phi); i++) {
if (i == num)
add_phi_arg(phi, new_node, gimple_phi_arg_edge(phi, i), loc);
}
}
static void change_orig_node(struct visited *visited, gimple stmt, const_tree orig_node, tree new_node, unsigned int num)
{
tree cast_lhs = cast_to_orig_type(visited, stmt, orig_node, new_node);
switch (gimple_code(stmt)) {
case GIMPLE_RETURN:
gimple_return_set_retval(as_a_greturn(stmt), cast_lhs);
break;
case GIMPLE_CALL:
gimple_call_set_arg(as_a_gcall(stmt), num - 1, cast_lhs);
break;
case GIMPLE_ASM:
change_size_overflow_asm_input(as_a_gasm(stmt), cast_lhs);
break;
case GIMPLE_ASSIGN:
change_field_write_rhs(as_a_gassign(stmt), orig_node, cast_lhs);
break;
case GIMPLE_PHI:
change_phi_arg(as_a_gphi(stmt), cast_lhs, num);
break;
default:
debug_gimple_stmt(stmt);
gcc_unreachable();
}
update_stmt(stmt);
}
// e.g., 3.8.2, 64, arch/x86/ia32/ia32_signal.c copy_siginfo_from_user32(): compat_ptr() u32 max
static bool skip_asm_cast(const_tree arg)
{
gimple def_stmt = get_def_stmt(arg);
if (!def_stmt || !gimple_assign_cast_p(def_stmt))
return false;
def_stmt = get_def_stmt(gimple_assign_rhs1(def_stmt));
if (is_size_overflow_asm(def_stmt))
return false;
return def_stmt && gimple_code(def_stmt) == GIMPLE_ASM;
}
static interesting_stmts_t create_interesting_stmts(interesting_stmts_t head, next_interesting_function_t next_node, tree orig_node, gimple first_stmt, unsigned int num)
{
interesting_stmts_t new_node;
new_node = (interesting_stmts_t )xmalloc(sizeof(*new_node));
new_node->first_stmt = first_stmt;
new_node->num = num;
new_node->orig_node = orig_node;
new_node->next = head;
new_node->next_node = next_node;
return new_node;
}
static void free_interesting_stmts(interesting_stmts_t head)
{
while (head) {
interesting_stmts_t cur = head->next;
free(head);
head = cur;
}
}
/* This function calls the main recursion function (expand) that duplicates the stmts. Before that it checks the intentional_overflow attribute,
* it decides whether the duplication is necessary or not. After expand() it changes the orig node to the duplicated node
* in the original stmt (first stmt) and it inserts the overflow check for the arg of the callee or for the return value.
*/
static interesting_stmts_t search_interesting_stmt(interesting_stmts_t head, next_interesting_function_t next_node, gimple first_stmt, tree orig_node, unsigned int num)
{
enum tree_code orig_code;
gcc_assert(orig_node != NULL_TREE);
if (is_gimple_constant(orig_node))
return head;
orig_code = TREE_CODE(orig_node);
gcc_assert(orig_code != FIELD_DECL && orig_code != FUNCTION_DECL);
if (skip_types(orig_node))
return head;
// find a defining marked caller argument or struct field for arg
if (check_intentional_size_overflow_asm_and_attribute(orig_node) != MARK_NO)
return head;
if (skip_asm_cast(orig_node))
return head;
return create_interesting_stmts(head, next_node, orig_node, first_stmt, num);
}
static bool is_signed_error_code_const(const_tree node)
{
HOST_WIDE_INT constant = tree_to_shwi(node);
return constant >= -4095 && constant <= -1;
}
static bool is_unsigned_error_code_const(const_tree node)
{
unsigned HOST_WIDE_INT constant = tree_to_uhwi(node);
// ulong -4095
if (constant >= 0xfffffffffffff001)
return true;
// uint -4095
return constant >= 0xfffff001;
}
static bool is_error_code_const(const_tree node)
{
enum machine_mode mode;
if (!is_gimple_constant(node))
return false;
mode = TYPE_MODE(TREE_TYPE(node));
if (mode != SImode && mode != DImode)
return false;
if (!TYPE_UNSIGNED(TREE_TYPE(node)) && is_signed_error_code_const(node))
return true;
return TYPE_UNSIGNED(TREE_TYPE(node)) && is_unsigned_error_code_const(node);
}
static bool has_error_code(gphi *phi)
{
unsigned int i, len = gimple_phi_num_args(phi);
for (i = 0; i < len; i++) {
const_tree arg = gimple_phi_arg_def(phi, i);
if (is_error_code_const(arg))
return true;
}
return false;
}
static interesting_stmts_t search_interesting_rets(interesting_stmts_t head, next_interesting_function_t next_node_ret, greturn *ret)
{
tree first_node;
if (!next_node_ret || next_node_ret->marked == ASM_STMT_SO_MARK)
return head;
first_node = gimple_return_retval(ret);
if (first_node == NULL_TREE)
return head;
return search_interesting_stmt(head, next_node_ret, ret, first_node, 0);
}
static void handle_binary_assign(interesting_stmts_t expand_from, gassign *assign, tree rhs)
{
tree new_node;
gimple def_orig_node;
new_node = expand(expand_from, rhs);
if (new_node == NULL_TREE)
return;
def_orig_node = get_def_stmt(rhs);
if (pointer_set_contains(expand_from->visited->no_cast_check, def_orig_node))
return;
change_orig_node(expand_from->visited, assign, rhs, new_node, 0);
check_size_overflow(expand_from, assign, TREE_TYPE(new_node), new_node, rhs, BEFORE_STMT);
}
static bool search_error_codes(gimple_set *visited_error_codes, interesting_stmts_t expand_from, tree lhs, bool error_code)
{
gimple def_stmt;
def_stmt = get_def_stmt(lhs);
if (!def_stmt || gimple_code(def_stmt) == GIMPLE_NOP)
return error_code;
if (pointer_set_insert(visited_error_codes, def_stmt))
return error_code;
if (is_gimple_constant(lhs))
return error_code;
if (skip_types(lhs))
return is_error_code_const(lhs);
switch (gimple_code(def_stmt)) {
case GIMPLE_CALL:
case GIMPLE_ASM:
return error_code;
case GIMPLE_ASSIGN: {
tree rhs1, rhs2;
gassign *assign = as_a_gassign(def_stmt);
switch (gimple_num_ops(assign)) {
case 2:
return search_error_codes(visited_error_codes, expand_from, gimple_assign_rhs1(def_stmt), error_code);
case 3:
if (!error_code)
return error_code;
/* Run stmt duplication from the binary assignment ops (rhs1 and rhs2)
* so that size_overflow checking skips the lhs of the last binary assignment
* before the error code PHI.
*/
rhs1 = gimple_assign_rhs1(assign);
handle_binary_assign(expand_from, assign, rhs1);
rhs2 = gimple_assign_rhs2(assign);
handle_binary_assign(expand_from, assign, rhs2);
return error_code;
}
gcc_unreachable();
}
case GIMPLE_PHI: {
unsigned int i;
error_code = has_error_code(as_a_gphi(def_stmt));
for (i = 0; i < gimple_phi_num_args(def_stmt); i++) {
error_code = search_error_codes(visited_error_codes, expand_from, gimple_phi_arg_def(def_stmt, i), error_code);
}
return error_code;
}
default:
debug_gimple_stmt(def_stmt);
error("%s: unknown gimple code", __func__);
gcc_unreachable();
}
}
static bool handle_error_codes(interesting_stmts_t expand_from)
{
bool error_code;
gimple_set *visited_error_codes;
// expand the data flow from a return stmt
if (expand_from->next_node->num != 0 || strcmp(expand_from->next_node->context, "fndecl"))
return false;
visited_error_codes = pointer_set_create();
error_code = search_error_codes(visited_error_codes, expand_from, expand_from->orig_node, false);
pointer_set_destroy(visited_error_codes);
return error_code;
}
static void handle_interesting_stmt(struct visited *visited, interesting_stmts_t head)
{
interesting_stmts_t cur;
for (cur = head; cur; cur = cur->next) {
tree new_node;
gimple orig_def_stmt;
cur->visited = visited;
if (handle_error_codes(cur))
continue;
new_node = expand(cur, cur->orig_node);
if (new_node == NULL_TREE)
continue;
orig_def_stmt = get_def_stmt(cur->orig_node);
if (pointer_set_contains(visited->no_cast_check, orig_def_stmt))
continue;
change_orig_node(visited, cur->first_stmt, cur->orig_node, new_node, cur->num);
check_size_overflow(cur, cur->first_stmt, TREE_TYPE(new_node), new_node, cur->orig_node, BEFORE_STMT);
}
}
static next_interesting_function_t get_interesting_function_next_node(tree decl, unsigned int num)
{
next_interesting_function_t next_node;
const struct size_overflow_hash *so_hash;
struct fn_raw_data raw_data;
initialize_raw_data(&raw_data);
raw_data.decl = decl;
raw_data.decl_str = DECL_NAME_POINTER(decl);
raw_data.num = num;
raw_data.marked = YES_SO_MARK;
so_hash = get_size_overflow_hash_entry_tree(&raw_data, SIZE_OVERFLOW);
if (so_hash)
return get_and_create_next_node_from_global_next_nodes(&raw_data, NULL);
if (!so_hash && raw_data.based_decl != SO_NONE)
return NULL;
next_node = get_global_next_interesting_function_entry_with_hash(&raw_data);
if (next_node && next_node->marked != NO_SO_MARK) {
switch (next_node->based_decl) {
case SO_FUNCTION:
if (check_fns)
return next_node;
return NULL;
case SO_FIELD:
if (check_fields)
return next_node;
return NULL;
case SO_VAR:
if (check_vars)
return next_node;
return NULL;
case SO_FUNCTION_POINTER:
if (check_fnptrs)
return next_node;
return NULL;
default:
gcc_unreachable();
}
}
return NULL;
}
tree handle_fnptr_assign(const_gimple stmt)
{
tree field, rhs, op0;
const_tree op0_type;
enum tree_code rhs_code;
// TODO skip binary assignments for now (fs/sync.c _591 = __bpf_call_base + _590;)
if (gimple_num_ops(stmt) != 2)
return NULL_TREE;
gcc_assert(gimple_num_ops(stmt) == 2);
// TODO skip asm_stmt for now
if (gimple_code(stmt) == GIMPLE_ASM)
return NULL_TREE;
rhs = gimple_assign_rhs1(stmt);
if (is_gimple_constant(rhs))
return NULL_TREE;
rhs_code = TREE_CODE(rhs);
if (rhs_code == VAR_DECL)
return rhs;
switch (rhs_code) {
case ADDR_EXPR:
op0 = TREE_OPERAND(rhs, 0);
if (TREE_CODE(op0) == FUNCTION_DECL)
return op0;
return NULL_TREE;
case COMPONENT_REF:
break;
// TODO skip array_ref for now
case ARRAY_REF:
return NULL_TREE;
// TODO skip ssa_name because it can lead to parm_decl
case SSA_NAME:
return NULL_TREE;
// TODO skip mem_ref and indirect_ref for now
#if BUILDING_GCC_VERSION >= 4006
case MEM_REF:
#endif
case INDIRECT_REF:
return NULL_TREE;
default:
debug_tree(rhs);
debug_gimple_stmt((gimple)stmt);
gcc_unreachable();
}
op0 = TREE_OPERAND(rhs, 0);
switch (TREE_CODE(op0)) {
// TODO skip array_ref and parm_decl for now
case ARRAY_REF:
case PARM_DECL:
return NULL_TREE;
case COMPONENT_REF:
#if BUILDING_GCC_VERSION >= 4006
case MEM_REF:
#endif
case INDIRECT_REF:
case VAR_DECL:
break;
default:
debug_tree(op0);
gcc_unreachable();
}
op0_type = TREE_TYPE(op0);
// TODO skip unions for now
if (TREE_CODE(op0_type) == UNION_TYPE)
return NULL_TREE;
gcc_assert(TREE_CODE(op0_type) == RECORD_TYPE);
field = TREE_OPERAND(rhs, 1);
gcc_assert(TREE_CODE(field) == FIELD_DECL);
return field;
}
static tree get_fn_or_fnptr_decl(const gcall *call_stmt)
{
const_tree fnptr;
const_gimple def_stmt;
tree decl = gimple_call_fndecl(call_stmt);
if (decl != NULL_TREE)
return decl;
fnptr = gimple_call_fn(call_stmt);
if (fnptr == NULL_TREE)
return NULL_TREE;
// !!! assertot kell irni 0-ra, mert csak az lehet ott
if (is_gimple_constant(fnptr))
return NULL_TREE;
def_stmt = get_fnptr_def_stmt(fnptr);
return handle_fnptr_assign(def_stmt);
}
// Start stmt duplication on marked function parameters
static interesting_stmts_t search_interesting_calls(interesting_stmts_t head, gcall *call_stmt)
{
tree decl;
unsigned int i, len;
len = gimple_call_num_args(call_stmt);
if (len == 0)
return head;
decl = get_fn_or_fnptr_decl(call_stmt);
if (decl == NULL_TREE)
return head;
for (i = 0; i < len; i++) {
tree arg;
next_interesting_function_t next_node;
arg = gimple_call_arg(call_stmt, i);
if (is_gimple_constant(arg))
continue;
if (skip_types(arg))
continue;
next_node = get_interesting_function_next_node(decl, i + 1);
if (next_node)
head = search_interesting_stmt(head, next_node, call_stmt, arg, i + 1);
}
return head;
}
// Find assignements to structure fields and vardecls
static interesting_stmts_t search_interesting_structs_vardecls(interesting_stmts_t head, gassign *assign)
{
enum intentional_mark mark;
next_interesting_function_t next_node;
tree rhs1, rhs2, lhs, decl;
#if BUILDING_GCC_VERSION >= 4006
tree rhs3;
#endif
lhs = gimple_assign_lhs(assign);
if (VAR_P(lhs))
decl = lhs;
else
decl = get_ref_field(lhs);
if (decl == NULL_TREE)
return head;
if (DECL_NAME(decl) == NULL_TREE)
return head;
if (is_bitfield_unnamed_cast(decl, assign))
return head;
next_node = get_interesting_function_next_node(decl, 0);
if (!next_node)
return head;
mark = get_intentional_attr_type(decl);
if (mark != MARK_NO)
return head;
rhs1 = gimple_assign_rhs1(assign);
head = search_interesting_stmt(head, next_node, assign, rhs1, 0);
rhs2 = gimple_assign_rhs2(assign);
if (rhs2)
head = search_interesting_stmt(head, next_node, assign, rhs2, 0);
#if BUILDING_GCC_VERSION >= 4006
rhs3 = gimple_assign_rhs3(assign);
if (rhs3)
head = search_interesting_stmt(head, next_node, assign, rhs3, 0);
#endif
return head;
}
static next_interesting_function_t create_so_asm_next_interesting_function_node(const gasm *stmt)
{
next_interesting_function_t next_node;
struct fn_raw_data raw_data;
initialize_raw_data(&raw_data);
raw_data.decl_str = gimple_asm_string(stmt);
raw_data.context = "attr";
raw_data.hash = 0;
raw_data.num = 0;
raw_data.marked = ASM_STMT_SO_MARK;
raw_data.based_decl = SO_FUNCTION;
next_node = get_global_next_interesting_function_entry(&raw_data);
if (next_node)
return next_node;
next_node = create_new_next_interesting_entry(&raw_data, NULL);
gcc_assert(next_node);
add_to_global_next_interesting_function(next_node);
return next_node;
}
// Collect interesting stmts for duplication
static void search_interesting_stmts(struct visited *visited)
{
basic_block bb;
next_interesting_function_t next_node_ret;
interesting_stmts_t head = NULL;
next_node_ret = get_interesting_function_next_node(current_function_decl, 0);
FOR_EACH_BB_FN(bb, cfun) {
gimple_stmt_iterator gsi;
for (gsi = gsi_start_bb(bb); !gsi_end_p(gsi); gsi_next(&gsi)) {
gimple stmt = gsi_stmt(gsi);
switch (gimple_code(stmt)) {
case GIMPLE_ASM: {
tree first_node;
next_interesting_function_t next_node;
const gasm *asm_stmt = as_a_gasm(stmt);
if (!check_fns)
continue;
if (!is_size_overflow_insert_check_asm(asm_stmt))
continue;
next_node = create_so_asm_next_interesting_function_node(asm_stmt);
first_node = get_size_overflow_asm_input(asm_stmt);
head = search_interesting_stmt(head, next_node, stmt, first_node, 0);
break;
}
case GIMPLE_RETURN:
head = search_interesting_rets(head, next_node_ret, as_a_greturn(stmt));
break;
case GIMPLE_CALL:
head = search_interesting_calls(head, as_a_gcall(stmt));
break;
case GIMPLE_ASSIGN:
/* !!! TODO LTO modeban nincs duplikalas a globalis valtozora, mert a tree mergek
* utan mar nem lehet megkulonboztetni attol a globalis valtozotol, aminek a scopeja csak a file
* igy a context nem vardecl lesz, hanem vardecl_filenev. De execute-ban kiirja, ha hianyzik a hash tablabol
* IPA-ban van duplikalas.
*/
head = search_interesting_structs_vardecls(head, as_a_gassign(stmt));
break;
default:
break;
}
}
}
handle_interesting_stmt(visited, head);
free_interesting_stmts(head);
}
static struct visited *create_visited(void)
{
struct visited *new_node;
new_node = (struct visited *)xmalloc(sizeof(*new_node));
new_node->stmts = pointer_set_create();
new_node->my_stmts = pointer_set_create();
new_node->skip_expr_casts = pointer_set_create();
new_node->no_cast_check = pointer_set_create();
return new_node;
}
static void free_visited(struct visited *visited)
{
pointer_set_destroy(visited->stmts);
pointer_set_destroy(visited->my_stmts);
pointer_set_destroy(visited->skip_expr_casts);
pointer_set_destroy(visited->no_cast_check);
free(visited);
}
// Remove the size_overflow asm stmt and create an assignment from the input and output of the asm
static void replace_size_overflow_asm_with_assign(gasm *asm_stmt, tree lhs, tree rhs)
{
gassign *assign;
gimple_stmt_iterator gsi;
// already removed
if (gimple_bb(asm_stmt) == NULL)
return;
gsi = gsi_for_stmt(asm_stmt);
assign = gimple_build_assign(lhs, rhs);
gsi_insert_before(&gsi, assign, GSI_SAME_STMT);
SSA_NAME_DEF_STMT(lhs) = assign;
gsi_remove(&gsi, true);
}
// Replace our asm stmts with assignments (they are no longer needed and may interfere with later optimizations)
static void remove_size_overflow_asm(gimple stmt)
{
gimple_stmt_iterator gsi;
tree input, output;
if (!is_size_overflow_asm(stmt))
return;
if (gimple_asm_noutputs(as_a_gasm(stmt)) == 0) {
gsi = gsi_for_stmt(stmt);
ipa_remove_stmt_references(cgraph_get_node(current_function_decl), stmt);
gsi_remove(&gsi, true);
return;
}
input = gimple_asm_input_op(as_a_gasm(stmt), 0);
output = gimple_asm_output_op(as_a_gasm(stmt), 0);
replace_size_overflow_asm_with_assign(as_a_gasm(stmt), TREE_VALUE(output), TREE_VALUE(input));
}
static void remove_all_size_overflow_asm(void)
{
basic_block bb;
FOR_EACH_BB_FN(bb, cfun) {
gimple_stmt_iterator si;
for (si = gsi_start_bb(bb); !gsi_end_p(si); gsi_next(&si))
remove_size_overflow_asm(gsi_stmt(si));
}
}
unsigned int size_overflow_function_transform(struct cgraph_node *node __unused)
{
struct visited *visited;
#if BUILDING_GCC_VERSION >= 4008
if (dump_file) {
fprintf(dump_file, "BEFORE TRANSFORM -------------------------\n");
size_overflow_dump_function(dump_file, node);
}
#endif
visited = create_visited();
set_dominance_info();
search_interesting_stmts(visited);
remove_all_size_overflow_asm();
unset_dominance_info();
free_visited(visited);
#if BUILDING_GCC_VERSION >= 4008
if (dump_file) {
fprintf(dump_file, "AFTER TRANSFORM -------------------------\n");
size_overflow_dump_function(dump_file, node);
}
#endif
return TODO_dump_func | TODO_verify_stmts | TODO_remove_unused_locals | TODO_update_ssa_no_phi | TODO_ggc_collect | TODO_verify_flow;
}