diff options
Diffstat (limited to 'gcc/ubsan.c')
-rw-r--r-- | gcc/ubsan.c | 337 |
1 files changed, 278 insertions, 59 deletions
diff --git a/gcc/ubsan.c b/gcc/ubsan.c index a16f3eb097f..7cc8c180ba8 100644 --- a/gcc/ubsan.c +++ b/gcc/ubsan.c @@ -1,5 +1,5 @@ /* UndefinedBehaviorSanitizer, undefined behavior detector. - Copyright (C) 2013 Free Software Foundation, Inc. + Copyright (C) 2013-2014 Free Software Foundation, Inc. Contributed by Marek Polacek <polacek@redhat.com> This file is part of GCC. @@ -27,6 +27,7 @@ along with GCC; see the file COPYING3. If not see #include "cgraph.h" #include "tree-pass.h" #include "tree-ssa-alias.h" +#include "tree-pretty-print.h" #include "internal-fn.h" #include "gimple-expr.h" #include "gimple.h" @@ -40,9 +41,11 @@ along with GCC; see the file COPYING3. If not see #include "cfgloop.h" #include "ubsan.h" #include "c-family/c-common.h" - -/* From trans-mem.c. */ -#define PROB_VERY_UNLIKELY (REG_BR_PROB_BASE / 2000 - 1) +#include "rtl.h" +#include "expr.h" +#include "tree-ssanames.h" +#include "asan.h" +#include "gimplify-me.h" /* Map from a tree to a VAR_DECL tree. */ @@ -105,45 +108,53 @@ decl_for_type_insert (tree type, tree decl) /* Helper routine, which encodes a value in the pointer_sized_int_node. Arguments with precision <= POINTER_SIZE are passed directly, - the rest is passed by reference. T is a value we are to encode. */ + the rest is passed by reference. T is a value we are to encode. + IN_EXPAND_P is true if this function is called during expansion. */ tree -ubsan_encode_value (tree t) +ubsan_encode_value (tree t, bool in_expand_p) { tree type = TREE_TYPE (t); - switch (TREE_CODE (type)) - { - case INTEGER_TYPE: - if (TYPE_PRECISION (type) <= POINTER_SIZE) + const unsigned int bitsize = GET_MODE_BITSIZE (TYPE_MODE (type)); + if (bitsize <= POINTER_SIZE) + switch (TREE_CODE (type)) + { + case BOOLEAN_TYPE: + case ENUMERAL_TYPE: + case INTEGER_TYPE: return fold_build1 (NOP_EXPR, pointer_sized_int_node, t); + case REAL_TYPE: + { + tree itype = build_nonstandard_integer_type (bitsize, true); + t = fold_build1 (VIEW_CONVERT_EXPR, itype, t); + return fold_convert (pointer_sized_int_node, t); + } + default: + gcc_unreachable (); + } + else + { + if (!DECL_P (t) || !TREE_ADDRESSABLE (t)) + { + /* The reason for this is that we don't want to pessimize + code by making vars unnecessarily addressable. */ + tree var = create_tmp_var (type, NULL); + tree tem = build2 (MODIFY_EXPR, void_type_node, var, t); + if (in_expand_p) + { + rtx mem + = assign_stack_temp_for_type (TYPE_MODE (type), + GET_MODE_SIZE (TYPE_MODE (type)), + type); + SET_DECL_RTL (var, mem); + expand_assignment (var, t, false); + return build_fold_addr_expr (var); + } + t = build_fold_addr_expr (var); + return build2 (COMPOUND_EXPR, TREE_TYPE (t), tem, t); + } else return build_fold_addr_expr (t); - case REAL_TYPE: - { - unsigned int bitsize = GET_MODE_BITSIZE (TYPE_MODE (type)); - if (bitsize <= POINTER_SIZE) - { - tree itype = build_nonstandard_integer_type (bitsize, true); - t = fold_build1 (VIEW_CONVERT_EXPR, itype, t); - return fold_convert (pointer_sized_int_node, t); - } - else - { - if (!TREE_ADDRESSABLE (t)) - { - /* The reason for this is that we don't want to pessimize - code by making vars unnecessarily addressable. */ - tree var = create_tmp_var (TREE_TYPE (t), NULL); - tree tem = build2 (MODIFY_EXPR, void_type_node, var, t); - t = build_fold_addr_expr (var); - return build2 (COMPOUND_EXPR, TREE_TYPE (t), tem, t); - } - else - return build_fold_addr_expr (t); - } - } - default: - gcc_unreachable (); } } @@ -229,13 +240,13 @@ ubsan_source_location (location_t loc) xloc = expand_location (loc); /* Fill in the values from LOC. */ - size_t len = xloc.file ? strlen (xloc.file) : 0; - tree str = build_string (len + 1, xloc.file ? xloc.file : ""); + size_t len = strlen (xloc.file); + tree str = build_string (len + 1, xloc.file); TREE_TYPE (str) = build_array_type (char_type_node, build_index_type (size_int (len))); TREE_READONLY (str) = 1; TREE_STATIC (str) = 1; - str = build_fold_addr_expr_loc (loc, str); + str = build_fold_addr_expr (str); tree ctor = build_constructor_va (type, 3, NULL_TREE, str, NULL_TREE, build_int_cst (unsigned_type_node, xloc.line), NULL_TREE, @@ -272,8 +283,12 @@ ubsan_type_descriptor (tree type, bool want_pointer_type_p) type = TYPE_MAIN_VARIANT (type); tree decl = decl_for_type_lookup (type); - if (decl != NULL_TREE) - return decl; + /* It is possible that some of the earlier created DECLs were found + unused, in that case they weren't emitted and varpool_get_node + returns NULL node on them. But now we really need them. Thus, + renew them here. */ + if (decl != NULL_TREE && varpool_get_node (decl)) + return build_fold_addr_expr (decl); tree dtype = ubsan_type_descriptor_type (); tree type2 = type; @@ -332,6 +347,8 @@ ubsan_type_descriptor (tree type, bool want_pointer_type_p) switch (TREE_CODE (type)) { + case BOOLEAN_TYPE: + case ENUMERAL_TYPE: case INTEGER_TYPE: tkind = 0x0000; break; @@ -372,11 +389,10 @@ ubsan_type_descriptor (tree type, bool want_pointer_type_p) DECL_INITIAL (decl) = ctor; rest_of_decl_compilation (decl, 1, 0); - /* Save the address of the VAR_DECL into the hash table. */ - decl = build_fold_addr_expr (decl); + /* Save the VAR_DECL into the hash table. */ decl_for_type_insert (type, decl); - return decl; + return build_fold_addr_expr (decl); } /* Create a structure for the ubsan library. NAME is a name of the new @@ -398,6 +414,7 @@ ubsan_create_data (const char *name, location_t loc, tree td_type = ubsan_type_descriptor_type (); TYPE_READONLY (td_type) = 1; td_type = build_pointer_type (td_type); + loc = LOCATION_LOCUS (loc); /* Create the structure type. */ ret = make_node (RECORD_TYPE); @@ -610,24 +627,213 @@ instrument_mem_ref (tree t, gimple_stmt_iterator *iter, bool is_lhs) gsi_insert_before (iter, g, GSI_SAME_STMT); } -/* Callback function for the pointer instrumentation. */ +/* Perform the pointer instrumentation. */ -static tree -instrument_null (tree *tp, int * /*walk_subtree*/, void *data) +static void +instrument_null (gimple_stmt_iterator gsi, bool is_lhs) { - tree t = *tp; + gimple stmt = gsi_stmt (gsi); + tree t = is_lhs ? gimple_get_lhs (stmt) : gimple_assign_rhs1 (stmt); + t = get_base_address (t); const enum tree_code code = TREE_CODE (t); - struct walk_stmt_info *wi = (struct walk_stmt_info *) data; - if (code == MEM_REF && TREE_CODE (TREE_OPERAND (t, 0)) == SSA_NAME) - instrument_mem_ref (TREE_OPERAND (t, 0), &wi->gsi, wi->is_lhs); + instrument_mem_ref (TREE_OPERAND (t, 0), &gsi, is_lhs); else if (code == ADDR_EXPR && POINTER_TYPE_P (TREE_TYPE (t)) && TREE_CODE (TREE_TYPE (TREE_TYPE (t))) == METHOD_TYPE) - instrument_member_call (&wi->gsi); + instrument_member_call (&gsi); +} + +/* Build an ubsan builtin call for the signed-integer-overflow + sanitization. CODE says what kind of builtin are we building, + LOC is a location, LHSTYPE is the type of LHS, OP0 and OP1 + are operands of the binary operation. */ + +tree +ubsan_build_overflow_builtin (tree_code code, location_t loc, tree lhstype, + tree op0, tree op1) +{ + tree data = ubsan_create_data ("__ubsan_overflow_data", loc, NULL, + ubsan_type_descriptor (lhstype, false), + NULL_TREE); + enum built_in_function fn_code; + + switch (code) + { + case PLUS_EXPR: + fn_code = BUILT_IN_UBSAN_HANDLE_ADD_OVERFLOW; + break; + case MINUS_EXPR: + fn_code = BUILT_IN_UBSAN_HANDLE_SUB_OVERFLOW; + break; + case MULT_EXPR: + fn_code = BUILT_IN_UBSAN_HANDLE_MUL_OVERFLOW; + break; + case NEGATE_EXPR: + fn_code = BUILT_IN_UBSAN_HANDLE_NEGATE_OVERFLOW; + break; + default: + gcc_unreachable (); + } + tree fn = builtin_decl_explicit (fn_code); + return build_call_expr_loc (loc, fn, 2 + (code != NEGATE_EXPR), + build_fold_addr_expr_loc (loc, data), + ubsan_encode_value (op0, true), + op1 ? ubsan_encode_value (op1, true) + : NULL_TREE); +} + +/* Perform the signed integer instrumentation. GSI is the iterator + pointing at statement we are trying to instrument. */ - return NULL_TREE; +static void +instrument_si_overflow (gimple_stmt_iterator gsi) +{ + gimple stmt = gsi_stmt (gsi); + tree_code code = gimple_assign_rhs_code (stmt); + tree lhs = gimple_assign_lhs (stmt); + tree lhstype = TREE_TYPE (lhs); + tree a, b; + gimple g; + + /* If this is not a signed operation, don't instrument anything here. + Also punt on bit-fields. */ + if (!INTEGRAL_TYPE_P (lhstype) + || TYPE_OVERFLOW_WRAPS (lhstype) + || GET_MODE_BITSIZE (TYPE_MODE (lhstype)) != TYPE_PRECISION (lhstype)) + return; + + switch (code) + { + case MINUS_EXPR: + case PLUS_EXPR: + case MULT_EXPR: + /* Transform + i = u {+,-,*} 5; + into + i = UBSAN_CHECK_{ADD,SUB,MUL} (u, 5); */ + a = gimple_assign_rhs1 (stmt); + b = gimple_assign_rhs2 (stmt); + g = gimple_build_call_internal (code == PLUS_EXPR + ? IFN_UBSAN_CHECK_ADD + : code == MINUS_EXPR + ? IFN_UBSAN_CHECK_SUB + : IFN_UBSAN_CHECK_MUL, 2, a, b); + gimple_call_set_lhs (g, lhs); + gsi_replace (&gsi, g, false); + break; + case NEGATE_EXPR: + /* Represent i = -u; + as + i = UBSAN_CHECK_SUB (0, u); */ + a = build_int_cst (lhstype, 0); + b = gimple_assign_rhs1 (stmt); + g = gimple_build_call_internal (IFN_UBSAN_CHECK_SUB, 2, a, b); + gimple_call_set_lhs (g, lhs); + gsi_replace (&gsi, g, false); + break; + default: + break; + } +} + +/* Instrument loads from (non-bitfield) bool and C++ enum values + to check if the memory value is outside of the range of the valid + type values. */ + +static void +instrument_bool_enum_load (gimple_stmt_iterator *gsi) +{ + gimple stmt = gsi_stmt (*gsi); + tree rhs = gimple_assign_rhs1 (stmt); + tree type = TREE_TYPE (rhs); + tree minv = NULL_TREE, maxv = NULL_TREE; + + if (TREE_CODE (type) == BOOLEAN_TYPE && (flag_sanitize & SANITIZE_BOOL)) + { + minv = boolean_false_node; + maxv = boolean_true_node; + } + else if (TREE_CODE (type) == ENUMERAL_TYPE + && (flag_sanitize & SANITIZE_ENUM) + && TREE_TYPE (type) != NULL_TREE + && TREE_CODE (TREE_TYPE (type)) == INTEGER_TYPE + && (TYPE_PRECISION (TREE_TYPE (type)) + < GET_MODE_PRECISION (TYPE_MODE (type)))) + { + minv = TYPE_MIN_VALUE (TREE_TYPE (type)); + maxv = TYPE_MAX_VALUE (TREE_TYPE (type)); + } + else + return; + + int modebitsize = GET_MODE_BITSIZE (TYPE_MODE (type)); + HOST_WIDE_INT bitsize, bitpos; + tree offset; + enum machine_mode mode; + int volatilep = 0, unsignedp = 0; + tree base = get_inner_reference (rhs, &bitsize, &bitpos, &offset, &mode, + &unsignedp, &volatilep, false); + tree utype = build_nonstandard_integer_type (modebitsize, 1); + + if ((TREE_CODE (base) == VAR_DECL && DECL_HARD_REGISTER (base)) + || (bitpos % modebitsize) != 0 + || bitsize != modebitsize + || GET_MODE_BITSIZE (TYPE_MODE (utype)) != modebitsize + || TREE_CODE (gimple_assign_lhs (stmt)) != SSA_NAME) + return; + + location_t loc = gimple_location (stmt); + tree ptype = build_pointer_type (TREE_TYPE (rhs)); + tree atype = reference_alias_ptr_type (rhs); + gimple g = gimple_build_assign (make_ssa_name (ptype, NULL), + build_fold_addr_expr (rhs)); + gimple_set_location (g, loc); + gsi_insert_before (gsi, g, GSI_SAME_STMT); + tree mem = build2 (MEM_REF, utype, gimple_assign_lhs (g), + build_int_cst (atype, 0)); + tree urhs = make_ssa_name (utype, NULL); + g = gimple_build_assign (urhs, mem); + gimple_set_location (g, loc); + gsi_insert_before (gsi, g, GSI_SAME_STMT); + minv = fold_convert (utype, minv); + maxv = fold_convert (utype, maxv); + if (!integer_zerop (minv)) + { + g = gimple_build_assign_with_ops (MINUS_EXPR, + make_ssa_name (utype, NULL), + urhs, minv); + gimple_set_location (g, loc); + gsi_insert_before (gsi, g, GSI_SAME_STMT); + } + + gimple_stmt_iterator gsi2 = *gsi; + basic_block then_bb, fallthru_bb; + *gsi = create_cond_insert_point (gsi, true, false, true, + &then_bb, &fallthru_bb); + g = gimple_build_cond (GT_EXPR, gimple_assign_lhs (g), + int_const_binop (MINUS_EXPR, maxv, minv), + NULL_TREE, NULL_TREE); + gimple_set_location (g, loc); + gsi_insert_after (gsi, g, GSI_NEW_STMT); + + gimple_assign_set_rhs_with_ops (&gsi2, NOP_EXPR, urhs, NULL_TREE); + update_stmt (stmt); + + tree data = ubsan_create_data ("__ubsan_invalid_value_data", + loc, NULL, + ubsan_type_descriptor (type, false), + NULL_TREE); + data = build_fold_addr_expr_loc (loc, data); + tree fn = builtin_decl_explicit (BUILT_IN_UBSAN_HANDLE_LOAD_INVALID_VALUE); + + gsi2 = gsi_after_labels (then_bb); + tree val = force_gimple_operand_gsi (&gsi2, ubsan_encode_value (urhs), + true, NULL_TREE, true, GSI_SAME_STMT); + g = gimple_build_call (fn, 2, data, val); + gimple_set_location (g, loc); + gsi_insert_before (&gsi2, g, GSI_SAME_STMT); } /* Gate and execute functions for ubsan pass. */ @@ -638,11 +844,10 @@ ubsan_pass (void) basic_block bb; gimple_stmt_iterator gsi; - FOR_EACH_BB (bb) + FOR_EACH_BB_FN (bb, cfun) { for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi);) { - struct walk_stmt_info wi; gimple stmt = gsi_stmt (gsi); if (is_gimple_debug (stmt) || gimple_clobber_p (stmt)) { @@ -650,9 +855,22 @@ ubsan_pass (void) continue; } - memset (&wi, 0, sizeof (wi)); - wi.gsi = gsi; - walk_gimple_op (stmt, instrument_null, &wi); + if ((flag_sanitize & SANITIZE_SI_OVERFLOW) + && is_gimple_assign (stmt)) + instrument_si_overflow (gsi); + + if (flag_sanitize & SANITIZE_NULL) + { + if (gimple_store_p (stmt)) + instrument_null (gsi, true); + if (gimple_assign_load_p (stmt)) + instrument_null (gsi, false); + } + + if (flag_sanitize & (SANITIZE_BOOL | SANITIZE_ENUM) + && gimple_assign_load_p (stmt)) + instrument_bool_enum_load (&gsi); + gsi_next (&gsi); } } @@ -662,7 +880,8 @@ ubsan_pass (void) static bool gate_ubsan (void) { - return flag_sanitize & SANITIZE_NULL; + return flag_sanitize & (SANITIZE_NULL | SANITIZE_SI_OVERFLOW + | SANITIZE_BOOL | SANITIZE_ENUM); } namespace { |