301 lines
8.9 KiB
C
301 lines
8.9 KiB
C
/*
|
|
* Copyright 2012-2017 by PaX Team <pageexec@freemail.hu>
|
|
* Licensed under the GPL v2
|
|
*
|
|
* Homepage: http://pax.grsecurity.net/
|
|
*/
|
|
|
|
#include "rap.h"
|
|
|
|
bool report_runtime;
|
|
bool report_fptr_hash;
|
|
|
|
static bool rap_fptr_gate(void)
|
|
{
|
|
if (require_call_hl_cfi)
|
|
return false;
|
|
else
|
|
return rap_cmodel_check();
|
|
}
|
|
|
|
static tree build_rap_hash(gimple call_stmt, tree fntype)
|
|
{
|
|
rap_hash_t hash;
|
|
|
|
hash = rap_hash_function_type(fntype, imprecise_rap_hash_flags);
|
|
if (report_fptr_hash)
|
|
inform(gimple_location(call_stmt), "fptr rap_hash: %x", hash.hash);
|
|
return build_int_cst_type(rap_hash_type_node, hash.hash);
|
|
}
|
|
|
|
// extract the rap_hash stored at an offset from ptr
|
|
tree get_rap_hash(gimple_seq *stmts, location_t loc, tree fptr, HOST_WIDE_INT rap_hash_offset)
|
|
{
|
|
gimple assign_hash;
|
|
tree target_hash;
|
|
#if BUILDING_GCC_VERSION == 4005
|
|
tree fptr2;
|
|
#endif
|
|
|
|
// target_hash = *(long*)((void*)fptr + rap_hash_offset)
|
|
target_hash = create_tmp_var(rap_hash_type_node, "rap_hash");
|
|
add_referenced_var(target_hash);
|
|
target_hash = make_ssa_name(target_hash, NULL);
|
|
#if BUILDING_GCC_VERSION == 4005
|
|
fptr2 = create_tmp_var(ptr_type_node, "rap_fptr2");
|
|
fptr2 = make_ssa_name(fptr2, NULL);
|
|
assign_hash = gimple_build_assign(fptr2, build2(POINTER_PLUS_EXPR, ptr_type_node, fptr, build_int_cst_type(sizetype, rap_hash_offset)));
|
|
gimple_set_location(assign_hash, loc);
|
|
SSA_NAME_DEF_STMT(fptr2) = assign_hash;
|
|
gimple_seq_add_stmt(stmts, assign_hash);
|
|
fptr = gimple_get_lhs(assign_hash);
|
|
|
|
fptr2 = create_tmp_var(build_pointer_type(rap_hash_type_node), "rap_fptr2");
|
|
fptr2 = make_ssa_name(fptr2, NULL);
|
|
assign_hash = gimple_build_assign(fptr2, fold_convert(build_pointer_type(TREE_TYPE(target_hash)), fptr));
|
|
gimple_set_location(assign_hash, loc);
|
|
SSA_NAME_DEF_STMT(fptr2) = assign_hash;
|
|
gimple_seq_add_stmt(stmts, assign_hash);
|
|
fptr = gimple_get_lhs(assign_hash);
|
|
|
|
assign_hash = gimple_build_assign(target_hash, build1(INDIRECT_REF, rap_hash_type_node, fptr));
|
|
#else
|
|
assign_hash = gimple_build_assign(target_hash, build2(MEM_REF, rap_hash_type_node, fptr, build_int_cst_type(build_pointer_type(rap_hash_type_node), rap_hash_offset)));
|
|
#endif
|
|
gimple_set_location(assign_hash, loc);
|
|
SSA_NAME_DEF_STMT(target_hash) = assign_hash;
|
|
gimple_seq_add_stmt(stmts, assign_hash);
|
|
|
|
return target_hash;
|
|
}
|
|
|
|
static void rap_mark_retloc(gimple_stmt_iterator *gsi, tree computed_hash)
|
|
{
|
|
gimple stmt;
|
|
VEC(tree, gc) *inputs = NULL;
|
|
VEC(tree, gc) *clobbers = NULL;
|
|
tree input, clobber, fndecl;
|
|
|
|
fndecl = gimple_call_fndecl(gsi_stmt(*gsi));
|
|
if (fndecl && is_simple_builtin(fndecl))
|
|
return;
|
|
|
|
input = build_tree_list(NULL_TREE, build_const_char_string(2, "i"));
|
|
input = chainon(NULL_TREE, build_tree_list(input, computed_hash));
|
|
VEC_safe_push(tree, gc, inputs, input);
|
|
|
|
clobber = build_tree_list(NULL_TREE, build_const_char_string(3, "cx"));
|
|
VEC_safe_push(tree, gc, clobbers, clobber);
|
|
|
|
stmt = gimple_build_asm_vec("", inputs, NULL, NULL, NULL);
|
|
gimple_asm_set_volatile(as_a_gasm(stmt), true);
|
|
gimple_set_location(stmt, gimple_location(gsi_stmt(*gsi)));
|
|
gsi_insert_before(gsi, stmt, GSI_SAME_STMT);
|
|
}
|
|
|
|
// check the function hash of the target of the fptr
|
|
static void rap_instrument_fptr(gimple_stmt_iterator *gsi)
|
|
{
|
|
gimple check_hash, call_stmt, stmt;
|
|
gimple_seq stmts = NULL;
|
|
location_t loc;
|
|
tree computed_hash, target_hash, fptr, fntype;
|
|
basic_block cond_bb, join_bb, true_bb;
|
|
edge e;
|
|
HOST_WIDE_INT rap_hash_offset;
|
|
|
|
call_stmt = gsi_stmt(*gsi);
|
|
loc = gimple_location(call_stmt);
|
|
fptr = gimple_call_fn(call_stmt);
|
|
fntype = TREE_TYPE(TREE_TYPE(fptr));
|
|
|
|
if (TREE_CODE(fntype) == FUNCTION_TYPE) {
|
|
computed_hash = build_rap_hash(call_stmt, fntype);
|
|
} else {
|
|
debug_tree(fntype);
|
|
gcc_unreachable();
|
|
}
|
|
|
|
/* Try rap optimization */
|
|
|
|
|
|
if (UNITS_PER_WORD == 8)
|
|
rap_hash_offset = 2 * sizeof(rap_hash_t);
|
|
else if (UNITS_PER_WORD == 4)
|
|
rap_hash_offset = sizeof(rap_hash_t);
|
|
else
|
|
gcc_unreachable();
|
|
|
|
target_hash = get_rap_hash(&stmts, loc, fptr, -rap_hash_offset);
|
|
gsi_insert_seq_before(gsi, stmts, GSI_SAME_STMT);
|
|
|
|
// compare target_hash against computed function hash
|
|
// bail out on mismatch
|
|
check_hash = gimple_build_cond(NE_EXPR, target_hash, computed_hash, NULL_TREE, NULL_TREE);
|
|
gimple_set_location(check_hash, loc);
|
|
gsi_insert_before(gsi, check_hash, GSI_NEW_STMT);
|
|
|
|
cond_bb = gimple_bb(gsi_stmt(*gsi));
|
|
gcc_assert(!gsi_end_p(*gsi));
|
|
gcc_assert(check_hash == gsi_stmt(*gsi));
|
|
|
|
e = split_block(cond_bb, gsi_stmt(*gsi));
|
|
cond_bb = e->src;
|
|
join_bb = e->dest;
|
|
e->flags = EDGE_FALSE_VALUE;
|
|
e->probability = REG_BR_PROB_BASE;
|
|
|
|
true_bb = create_empty_bb(EXIT_BLOCK_PTR_FOR_FN(cfun)->prev_bb);
|
|
make_edge(cond_bb, true_bb, EDGE_TRUE_VALUE | EDGE_PRESERVE);
|
|
|
|
gcc_assert(dom_info_available_p(CDI_DOMINATORS));
|
|
set_immediate_dominator(CDI_DOMINATORS, true_bb, cond_bb);
|
|
set_immediate_dominator(CDI_DOMINATORS, join_bb, cond_bb);
|
|
|
|
gcc_assert(cond_bb->loop_father == join_bb->loop_father);
|
|
add_bb_to_loop(true_bb, cond_bb->loop_father);
|
|
|
|
*gsi = gsi_start_bb(true_bb);
|
|
|
|
if (report_runtime) {
|
|
VEC(tree, gc) *inputs = NULL;
|
|
tree input;
|
|
|
|
// build the equivalence of asm volatile ("" : : "a"(fptr));
|
|
input = build_tree_list(NULL_TREE, build_const_char_string(2, "a"));
|
|
input = chainon(NULL_TREE, build_tree_list(input, fptr));
|
|
VEC_safe_push(tree, gc, inputs, input);
|
|
|
|
stmt = gimple_build_asm_vec("", inputs, NULL, NULL, NULL);
|
|
gimple_asm_set_volatile(as_a_gasm(stmt), true);
|
|
gimple_set_location(stmt, loc);
|
|
gsi_insert_after(gsi, stmt, GSI_CONTINUE_LINKING);
|
|
}
|
|
|
|
if (rap_abort_call) {
|
|
stmt = gimple_build_asm_vec(rap_abort_call, NULL, NULL, NULL, NULL);
|
|
gimple_asm_set_volatile(as_a_gasm(stmt), true);
|
|
gimple_set_location(stmt, loc);
|
|
gsi_insert_after(gsi, stmt, GSI_CONTINUE_LINKING);
|
|
|
|
stmt = gimple_build_call(builtin_decl_implicit(BUILT_IN_UNREACHABLE), 0);
|
|
} else {
|
|
// this fake dependency is to prevent PRE from merging this BB with others of the same kind
|
|
stmt = barrier(fptr, false);
|
|
gimple_set_location(stmt, loc);
|
|
gsi_insert_after(gsi, stmt, GSI_CONTINUE_LINKING);
|
|
|
|
stmt = gimple_build_call(builtin_decl_implicit(BUILT_IN_TRAP), 0);
|
|
}
|
|
|
|
gimple_set_location(stmt, loc);
|
|
gsi_insert_after(gsi, stmt, GSI_CONTINUE_LINKING);
|
|
|
|
*gsi = gsi_start_bb(join_bb);
|
|
}
|
|
|
|
// find all language level function pointer dereferences and verify the target function
|
|
static unsigned int rap_fptr_execute(void)
|
|
{
|
|
basic_block bb;
|
|
|
|
loop_optimizer_init(LOOPS_NORMAL | LOOPS_HAVE_RECORDED_EXITS);
|
|
gcc_assert(current_loops);
|
|
|
|
calculate_dominance_info(CDI_DOMINATORS);
|
|
calculate_dominance_info(CDI_POST_DOMINATORS);
|
|
|
|
// 1. loop through BBs and GIMPLE statements
|
|
FOR_EACH_BB_FN(bb, cfun) {
|
|
gimple_stmt_iterator gsi;
|
|
|
|
for (gsi = gsi_start_bb(bb); !gsi_end_p(gsi); gsi_next(&gsi)) {
|
|
// gimple match: h_1 = get_fptr (); D.2709_3 = h_1 (x_2(D));
|
|
tree fptr, fntype, computed_hash;
|
|
gimple call_stmt;
|
|
rap_hash_t hash;
|
|
|
|
// is it a call ...
|
|
call_stmt = gsi_stmt(gsi);
|
|
if (!is_gimple_call(call_stmt))
|
|
continue;
|
|
|
|
fptr = gimple_call_fn(call_stmt);
|
|
if (!fptr)
|
|
continue;
|
|
|
|
switch (TREE_CODE(fptr)) {
|
|
default:
|
|
debug_gimple_stmt(call_stmt);
|
|
debug_tree(fptr);
|
|
debug_tree(TREE_TYPE(fptr));
|
|
gcc_unreachable();
|
|
|
|
case ADDR_EXPR:
|
|
hash = rap_hash_function_type(TREE_TYPE(TREE_OPERAND(fptr, 0)), imprecise_rap_hash_flags);
|
|
computed_hash = build_int_cst_type(rap_hash_type_node, -hash.hash);
|
|
rap_mark_retloc(&gsi, computed_hash);
|
|
continue;
|
|
|
|
case SSA_NAME:
|
|
if (SSA_NAME_VAR(fptr) == NULL_TREE)
|
|
break;
|
|
|
|
switch (TREE_CODE(SSA_NAME_VAR(fptr))) {
|
|
default:
|
|
debug_gimple_stmt(call_stmt);
|
|
debug_tree(fptr);
|
|
gcc_unreachable();
|
|
|
|
case VAR_DECL:
|
|
case PARM_DECL:
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case INTEGER_CST:
|
|
case OBJ_TYPE_REF:
|
|
break;
|
|
}
|
|
|
|
// ... through a function pointer
|
|
fntype = TREE_TYPE(fptr);
|
|
if (TREE_CODE(fntype) != POINTER_TYPE) {
|
|
gcc_assert(TREE_CODE(fntype) == FUNCTION_TYPE || TREE_CODE(fntype) == METHOD_TYPE);
|
|
hash = rap_hash_function_type(fntype, imprecise_rap_hash_flags);
|
|
computed_hash = build_int_cst_type(rap_hash_type_node, -hash.hash);
|
|
rap_mark_retloc(&gsi, computed_hash);
|
|
continue;
|
|
}
|
|
|
|
fntype = TREE_TYPE(fntype);
|
|
gcc_assert(TREE_CODE(fntype) == FUNCTION_TYPE || TREE_CODE(fntype) == METHOD_TYPE);
|
|
|
|
if (enable_type_call) {
|
|
rap_instrument_fptr(&gsi);
|
|
bb = gsi_bb(gsi);
|
|
gcc_assert(call_stmt == gsi_stmt(gsi));
|
|
}
|
|
|
|
if (enable_type_ret) {
|
|
hash = rap_hash_function_type(fntype, imprecise_rap_hash_flags);
|
|
computed_hash = build_int_cst_type(rap_hash_type_node, -hash.hash);
|
|
rap_mark_retloc(&gsi, computed_hash);
|
|
}
|
|
|
|
bb = gsi_bb(gsi);
|
|
gcc_assert(call_stmt == gsi_stmt(gsi));
|
|
}
|
|
}
|
|
|
|
free_dominance_info(CDI_DOMINATORS);
|
|
free_dominance_info(CDI_POST_DOMINATORS);
|
|
loop_optimizer_finalize();
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define PASS_NAME rap_fptr
|
|
#define TODO_FLAGS_FINISH TODO_verify_ssa | TODO_verify_stmts | TODO_dump_func | TODO_remove_unused_locals | TODO_update_ssa | TODO_cleanup_cfg | TODO_rebuild_cgraph_edges | TODO_verify_flow
|
|
#include "gcc-generate-gimple-pass.h"
|