381 lines
9.0 KiB
C
381 lines
9.0 KiB
C
|
/*
|
||
|
* Copyright 2012-2016 by PaX Team <pageexec@freemail.hu>
|
||
|
* Licensed under the GPL v2
|
||
|
*
|
||
|
* Homepage: http://pax.grsecurity.net/
|
||
|
*/
|
||
|
|
||
|
#include "rap.h"
|
||
|
|
||
|
static rap_hash_t *rap_imprecise_hashes;
|
||
|
static int rap_cgraph_max_uid;
|
||
|
|
||
|
static void rap_hash_function(const_tree fntype, rap_hash_flags_t flags, unsigned char sip_hash[8]);
|
||
|
|
||
|
static const unsigned char rap_hash_tree_code[MAX_TREE_CODES] = {
|
||
|
[0] = 0,
|
||
|
[1] = 0,
|
||
|
[2] = 0,
|
||
|
[3] = 0,
|
||
|
[4] = 0,
|
||
|
[OFFSET_TYPE] = 10,
|
||
|
[ENUMERAL_TYPE] = 20,
|
||
|
[BOOLEAN_TYPE] = 30,
|
||
|
[INTEGER_TYPE] = 40,
|
||
|
[REAL_TYPE] = 50,
|
||
|
[POINTER_TYPE] = 60,
|
||
|
[REFERENCE_TYPE] = 70,
|
||
|
#if BUILDING_GCC_VERSION >= 4006
|
||
|
[NULLPTR_TYPE] = 80,
|
||
|
#endif
|
||
|
[FIXED_POINT_TYPE] = 0,
|
||
|
[COMPLEX_TYPE] = 100,
|
||
|
[VECTOR_TYPE] = 110,
|
||
|
[ARRAY_TYPE] = 120,
|
||
|
[RECORD_TYPE] = 130,
|
||
|
[UNION_TYPE] = 140,
|
||
|
[QUAL_UNION_TYPE] = 0,
|
||
|
[VOID_TYPE] = 160,
|
||
|
#if BUILDING_GCC_VERSION >= 5000
|
||
|
[POINTER_BOUNDS_TYPE] = 170,
|
||
|
#endif
|
||
|
[FUNCTION_TYPE] = 180,
|
||
|
[METHOD_TYPE] = 0,
|
||
|
[LANG_TYPE] = 0,
|
||
|
};
|
||
|
|
||
|
static void rap_fold_hash(unsigned char *out, const unsigned char *in, unsigned long long inlen)
|
||
|
{
|
||
|
static const unsigned char rap_sip_key[16] = {
|
||
|
'P', 'a', 'X', ' ', 'T', 'e', 'a', 'm',
|
||
|
'R', 'A', 'P', ' ', 'H', 'A', 'S', 'H',
|
||
|
};
|
||
|
|
||
|
siphash24fold(out, in, inlen, rap_sip_key);
|
||
|
}
|
||
|
|
||
|
// compute the final hash value in the range [1,INT_MAX]
|
||
|
// the % and +1 trick leaves the value 0 available for marking non-indirectly callable functions
|
||
|
// and INT_MIN (0x80000000) for longjmp targets (sign extended)
|
||
|
// return places will use the (sign extended) range [INT_MIN+1,-1] ([0x8000001,0xffffffff])
|
||
|
static rap_hash_t rap_extract_hash(const unsigned char sip_hash[8])
|
||
|
{
|
||
|
rap_hash_t hash;
|
||
|
unsigned long long dividend, divisor;
|
||
|
|
||
|
memcpy(÷nd, sip_hash, sizeof dividend);
|
||
|
// divisor = 1ULL << (sizeof hash * 8 - 1);
|
||
|
// divisor |= divisor - 1;
|
||
|
divisor = 0x7fffffffUL;
|
||
|
hash.hash = dividend % divisor + 1;
|
||
|
return hash;
|
||
|
}
|
||
|
|
||
|
static void rap_hash_type_name(const_tree type, unsigned char sip_hash[8])
|
||
|
{
|
||
|
const_tree name = type_name(TYPE_MAIN_VARIANT(type));
|
||
|
|
||
|
// handle typedefs of anonymous structs/unions
|
||
|
if (name == NULL_TREE)
|
||
|
name = type_name(type);
|
||
|
|
||
|
if (name == NULL_TREE)
|
||
|
return;
|
||
|
|
||
|
gcc_assert(TREE_CODE(name) == IDENTIFIER_NODE);
|
||
|
rap_fold_hash(sip_hash, (const unsigned char *)IDENTIFIER_POINTER(name), IDENTIFIER_LENGTH(name));
|
||
|
}
|
||
|
|
||
|
static void rap_hash_type_precision(const_tree type, unsigned char sip_hash[8])
|
||
|
{
|
||
|
unsigned HOST_WIDE_INT size;
|
||
|
|
||
|
gcc_assert(TYPE_PRECISION(type));
|
||
|
|
||
|
size = TYPE_PRECISION(type);
|
||
|
rap_fold_hash(sip_hash, (const unsigned char *)&size, sizeof size);
|
||
|
}
|
||
|
|
||
|
const_tree type_name(const_tree type)
|
||
|
{
|
||
|
const_tree name;
|
||
|
|
||
|
name = TYPE_NAME(type);
|
||
|
if (!name)
|
||
|
return NULL_TREE;
|
||
|
|
||
|
switch (TREE_CODE(name)) {
|
||
|
case IDENTIFIER_NODE:
|
||
|
return name;
|
||
|
|
||
|
case TYPE_DECL:
|
||
|
gcc_assert(DECL_NAME(name));
|
||
|
return DECL_NAME(name);
|
||
|
|
||
|
default:
|
||
|
gcc_unreachable();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// the core computation of the rap hash
|
||
|
// the first piece is a (hopefully) compiler independent encondig of the type, derived from the gcc tree code
|
||
|
// the second piece is type specific information, such as the size, qualifiers, (recursively) referenced types, etc
|
||
|
static void rap_hash_tree(const_tree type, rap_hash_flags_t flags, unsigned char sip_hash[8])
|
||
|
{
|
||
|
enum tree_code code;
|
||
|
unsigned int attrs;
|
||
|
|
||
|
code = TREE_CODE(type);
|
||
|
attrs = rap_hash_tree_code[code];
|
||
|
if (!attrs) {
|
||
|
fprintf(stderr, "unhandled tree_code %s %d\n", get_tree_code_name(code), code);
|
||
|
debug_tree(type);
|
||
|
gcc_unreachable();
|
||
|
}
|
||
|
rap_fold_hash(sip_hash, (const unsigned char *)&attrs, sizeof attrs);
|
||
|
|
||
|
enum {
|
||
|
// attrs layout for
|
||
|
// - all types:
|
||
|
RAP_HASH_VOLATILE = 1U << 31,
|
||
|
RAP_HASH_NOT_VOLATILE = 1U << 30,
|
||
|
RAP_HASH_CONST = 1U << 29,
|
||
|
RAP_HASH_NOT_CONST = 1U << 28,
|
||
|
|
||
|
// - pointer types:
|
||
|
RAP_HASH_RESTRICT = 1U << 27,
|
||
|
RAP_HASH_NOT_RESTRICT = 1U << 26,
|
||
|
|
||
|
// - C integer types:
|
||
|
RAP_HASH_UNSIGNED = 1U << 25,
|
||
|
RAP_HASH_SIGNED = 1U << 24,
|
||
|
|
||
|
RAP_HASH_UNQUALIFIED_CHAR = 1U << 23,
|
||
|
RAP_HASH_CHAR = 1U << 22,
|
||
|
RAP_HASH_SHORT = 1U << 21,
|
||
|
RAP_HASH_INT = 1U << 20,
|
||
|
RAP_HASH_LONG = 1U << 19,
|
||
|
RAP_HASH_LONG_LONG = 1U << 18,
|
||
|
RAP_HASH_WCHAR = 1U << 17,
|
||
|
RAP_HASH_CHAR16 = 1U << 16,
|
||
|
RAP_HASH_CHAR32 = 1U << 15,
|
||
|
|
||
|
// - C float types
|
||
|
RAP_HASH_FLOAT = 1U << 14,
|
||
|
RAP_HASH_DOUBLE = 1U << 13,
|
||
|
RAP_HASH_LONG_DOUBLE = 1U << 12,
|
||
|
RAP_HASH_DFLOAT32 = 1U << 11,
|
||
|
RAP_HASH_DFLOAT64 = 1U << 10,
|
||
|
RAP_HASH_DFLOAT128 = 1U << 9,
|
||
|
};
|
||
|
|
||
|
attrs = 0;
|
||
|
if (flags.qual_volatile)
|
||
|
attrs |= TYPE_VOLATILE(type) ? RAP_HASH_VOLATILE : RAP_HASH_NOT_VOLATILE;
|
||
|
if (flags.qual_const)
|
||
|
attrs |= TYPE_READONLY(type) ? RAP_HASH_CONST : RAP_HASH_NOT_CONST;
|
||
|
|
||
|
switch (code) {
|
||
|
default:
|
||
|
debug_tree(type);
|
||
|
gcc_unreachable();
|
||
|
break;
|
||
|
|
||
|
case VOID_TYPE:
|
||
|
break;
|
||
|
|
||
|
case OFFSET_TYPE:
|
||
|
rap_hash_tree(TREE_TYPE(type), flags, sip_hash);
|
||
|
rap_hash_tree(TYPE_OFFSET_BASETYPE(type), flags, sip_hash);
|
||
|
break;
|
||
|
|
||
|
case FUNCTION_TYPE:
|
||
|
rap_hash_function(type, flags, sip_hash);
|
||
|
break;
|
||
|
|
||
|
case RECORD_TYPE:
|
||
|
rap_hash_type_name(type, sip_hash);
|
||
|
break;
|
||
|
|
||
|
case UNION_TYPE:
|
||
|
rap_hash_type_name(type, sip_hash);
|
||
|
break;
|
||
|
|
||
|
case POINTER_TYPE:
|
||
|
case REFERENCE_TYPE:
|
||
|
rap_hash_tree(TREE_TYPE(type), flags, sip_hash);
|
||
|
break;
|
||
|
|
||
|
case VECTOR_TYPE:
|
||
|
rap_hash_tree(TREE_TYPE(type), flags, sip_hash);
|
||
|
rap_hash_type_precision(TREE_TYPE(type), sip_hash);
|
||
|
break;
|
||
|
|
||
|
case ARRAY_TYPE:
|
||
|
rap_hash_tree(TREE_TYPE(type), flags, sip_hash);
|
||
|
break;
|
||
|
|
||
|
case REAL_TYPE: {
|
||
|
const_tree main_variant = TYPE_MAIN_VARIANT(type);
|
||
|
|
||
|
switch (TYPE_PRECISION(main_variant)) {
|
||
|
default:
|
||
|
debug_tree(type);
|
||
|
debug_tree(TYPE_MAIN_VARIANT(type));
|
||
|
gcc_unreachable();
|
||
|
|
||
|
case 32:
|
||
|
// attrs |= RAP_HASH_FLOAT;
|
||
|
break;
|
||
|
|
||
|
case 64:
|
||
|
// attrs |= RAP_HASH_DOUBLE;
|
||
|
break;
|
||
|
|
||
|
case 80:
|
||
|
case 128:
|
||
|
attrs |= RAP_HASH_LONG_DOUBLE;
|
||
|
break;
|
||
|
}
|
||
|
rap_hash_type_precision(main_variant, sip_hash);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case ENUMERAL_TYPE:
|
||
|
rap_hash_type_name(type, sip_hash);
|
||
|
case BOOLEAN_TYPE:
|
||
|
rap_hash_type_precision(type, sip_hash);
|
||
|
break;
|
||
|
|
||
|
case INTEGER_TYPE: {
|
||
|
attrs |= TYPE_UNSIGNED(type) ? RAP_HASH_UNSIGNED : RAP_HASH_SIGNED;
|
||
|
rap_hash_type_precision(type, sip_hash);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
rap_fold_hash(sip_hash, (const unsigned char *)&attrs, sizeof attrs);
|
||
|
}
|
||
|
|
||
|
static const_tree rap_dequal_argtype(const_tree argtype)
|
||
|
{
|
||
|
// since gcc/tree.c:free_lang_data_in_type removes const/volatile from the top level param decl
|
||
|
// we have to simulate it here as this can be called earlier from the frontend as well
|
||
|
if (TYPE_READONLY(argtype) || TYPE_VOLATILE(argtype)) {
|
||
|
int quals;
|
||
|
|
||
|
quals = TYPE_QUALS(argtype) & ~TYPE_QUAL_CONST & ~TYPE_QUAL_VOLATILE;
|
||
|
argtype = build_qualified_type(CONST_CAST_TREE(argtype), quals);
|
||
|
}
|
||
|
|
||
|
return argtype;
|
||
|
}
|
||
|
|
||
|
// main function to compute the rap hash for function types
|
||
|
// while virtual class methods are always replaced with their ancestor,
|
||
|
// callers can decide whether to fully utilize that information via flags.method_ancestor
|
||
|
static void rap_hash_function(const_tree fntype, rap_hash_flags_t flags, unsigned char sip_hash[8])
|
||
|
{
|
||
|
function_args_iterator args_iter;
|
||
|
const_tree arg;
|
||
|
|
||
|
switch (TREE_CODE(fntype)) {
|
||
|
default:
|
||
|
debug_tree(fntype);
|
||
|
gcc_unreachable();
|
||
|
|
||
|
case FUNCTION_TYPE:
|
||
|
// 1. hash the result
|
||
|
rap_hash_tree(TREE_TYPE(fntype), flags, sip_hash);
|
||
|
|
||
|
// 2. hash the function parameters
|
||
|
FOREACH_FUNCTION_ARGS(fntype, arg, args_iter) {
|
||
|
const_tree argtype = arg;
|
||
|
|
||
|
argtype = rap_dequal_argtype(argtype);
|
||
|
rap_hash_tree(argtype, flags, sip_hash);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
rap_hash_t rap_hash_function_type(const_tree fntype, rap_hash_flags_t flags)
|
||
|
{
|
||
|
unsigned char sip_hash[8] = { };
|
||
|
rap_hash_t hash;
|
||
|
|
||
|
rap_hash_function(fntype, flags, sip_hash);
|
||
|
hash = rap_extract_hash(sip_hash);
|
||
|
|
||
|
gcc_assert(hash.hash);
|
||
|
return hash;
|
||
|
}
|
||
|
|
||
|
rap_hash_t rap_hash_function_decl(const_tree fndecl, rap_hash_flags_t flags)
|
||
|
{
|
||
|
tree fntype;
|
||
|
|
||
|
gcc_assert(TREE_CODE(fndecl) == FUNCTION_DECL);
|
||
|
fntype = TREE_TYPE(fndecl);
|
||
|
|
||
|
switch (TREE_CODE(fntype)) {
|
||
|
default:
|
||
|
debug_tree(fndecl);
|
||
|
gcc_unreachable();
|
||
|
|
||
|
case FUNCTION_TYPE:
|
||
|
return rap_hash_function_type(fntype, flags);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
rap_hash_t rap_hash_function_node_imprecise(cgraph_node_ptr node)
|
||
|
{
|
||
|
rap_hash_t hash;
|
||
|
tree fndecl;
|
||
|
|
||
|
gcc_assert(rap_imprecise_hashes);
|
||
|
|
||
|
hash.hash = 0;
|
||
|
if (node->uid < rap_cgraph_max_uid)
|
||
|
hash = rap_imprecise_hashes[node->uid];
|
||
|
|
||
|
if (hash.hash)
|
||
|
return hash;
|
||
|
|
||
|
fndecl = NODE_DECL(node);
|
||
|
if (TREE_CODE(TREE_TYPE(fndecl)) == FUNCTION_TYPE)
|
||
|
return rap_hash_function_decl(fndecl, imprecise_rap_hash_flags);
|
||
|
|
||
|
debug_cgraph_node(node);
|
||
|
debug_tree(fndecl);
|
||
|
error("indirect call to function %qD with a reserved hash value", fndecl);
|
||
|
return hash;
|
||
|
}
|
||
|
|
||
|
void rap_calculate_func_hashes(void *event_data __unused, void *data __unused)
|
||
|
{
|
||
|
cgraph_node_ptr node;
|
||
|
int uid;
|
||
|
|
||
|
gcc_assert(!rap_imprecise_hashes);
|
||
|
|
||
|
rap_imprecise_hashes = (rap_hash_t *)xcalloc(cgraph_max_uid, sizeof(*rap_imprecise_hashes));
|
||
|
rap_cgraph_max_uid = cgraph_max_uid;
|
||
|
|
||
|
FOR_EACH_FUNCTION(node) {
|
||
|
const_tree fndecl;
|
||
|
|
||
|
uid = node->uid;
|
||
|
gcc_assert(uid < rap_cgraph_max_uid);
|
||
|
|
||
|
if (node->global.inlined_to)
|
||
|
continue;
|
||
|
|
||
|
fndecl = NODE_DECL(node);
|
||
|
gcc_assert(fndecl);
|
||
|
|
||
|
rap_imprecise_hashes[uid] = rap_hash_function_decl(fndecl, imprecise_rap_hash_flags);
|
||
|
gcc_assert(rap_imprecise_hashes[uid].hash);
|
||
|
}
|
||
|
}
|