Mercurial > hg > CbC > CbC_gcc
diff gcc/c-family/c-ubsan.c @ 111:04ced10e8804
gcc 7
author | kono |
---|---|
date | Fri, 27 Oct 2017 22:46:09 +0900 |
parents | |
children | 84e7813d76e9 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gcc/c-family/c-ubsan.c Fri Oct 27 22:46:09 2017 +0900 @@ -0,0 +1,505 @@ +/* UndefinedBehaviorSanitizer, undefined behavior detector. + Copyright (C) 2013-2017 Free Software Foundation, Inc. + Contributed by Marek Polacek <polacek@redhat.com> + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation; either version 3, or (at your option) any later +version. + +GCC is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +<http://www.gnu.org/licenses/>. */ + +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "tm.h" +#include "c-family/c-common.h" +#include "ubsan.h" +#include "c-family/c-ubsan.h" +#include "stor-layout.h" +#include "builtins.h" +#include "gimplify.h" +#include "stringpool.h" +#include "attribs.h" +#include "asan.h" + +/* Instrument division by zero and INT_MIN / -1. If not instrumenting, + return NULL_TREE. */ + +tree +ubsan_instrument_division (location_t loc, tree op0, tree op1) +{ + tree t, tt; + tree type = TREE_TYPE (op0); + + /* At this point both operands should have the same type, + because they are already converted to RESULT_TYPE. + Use TYPE_MAIN_VARIANT since typedefs can confuse us. */ + gcc_assert (TYPE_MAIN_VARIANT (TREE_TYPE (op0)) + == TYPE_MAIN_VARIANT (TREE_TYPE (op1))); + + op0 = unshare_expr (op0); + op1 = unshare_expr (op1); + + if (TREE_CODE (type) == INTEGER_TYPE + && sanitize_flags_p (SANITIZE_DIVIDE)) + t = fold_build2 (EQ_EXPR, boolean_type_node, + op1, build_int_cst (type, 0)); + else if (TREE_CODE (type) == REAL_TYPE + && sanitize_flags_p (SANITIZE_FLOAT_DIVIDE)) + t = fold_build2 (EQ_EXPR, boolean_type_node, + op1, build_real (type, dconst0)); + else + return NULL_TREE; + + /* We check INT_MIN / -1 only for signed types. */ + if (TREE_CODE (type) == INTEGER_TYPE + && sanitize_flags_p (SANITIZE_DIVIDE) + && !TYPE_UNSIGNED (type)) + { + tree x; + tt = fold_build2 (EQ_EXPR, boolean_type_node, unshare_expr (op1), + build_int_cst (type, -1)); + x = fold_build2 (EQ_EXPR, boolean_type_node, op0, + TYPE_MIN_VALUE (type)); + x = fold_build2 (TRUTH_AND_EXPR, boolean_type_node, x, tt); + t = fold_build2 (TRUTH_OR_EXPR, boolean_type_node, t, x); + } + + /* If the condition was folded to 0, no need to instrument + this expression. */ + if (integer_zerop (t)) + return NULL_TREE; + + /* In case we have a SAVE_EXPR in a conditional context, we need to + make sure it gets evaluated before the condition. */ + t = fold_build2 (COMPOUND_EXPR, TREE_TYPE (t), unshare_expr (op0), t); + t = fold_build2 (COMPOUND_EXPR, TREE_TYPE (t), unshare_expr (op1), t); + if (flag_sanitize_undefined_trap_on_error) + tt = build_call_expr_loc (loc, builtin_decl_explicit (BUILT_IN_TRAP), 0); + else + { + tree data = ubsan_create_data ("__ubsan_overflow_data", 1, &loc, + ubsan_type_descriptor (type), NULL_TREE, + NULL_TREE); + data = build_fold_addr_expr_loc (loc, data); + enum built_in_function bcode + = (flag_sanitize_recover & SANITIZE_DIVIDE) + ? BUILT_IN_UBSAN_HANDLE_DIVREM_OVERFLOW + : BUILT_IN_UBSAN_HANDLE_DIVREM_OVERFLOW_ABORT; + tt = builtin_decl_explicit (bcode); + op0 = unshare_expr (op0); + op1 = unshare_expr (op1); + tt = build_call_expr_loc (loc, tt, 3, data, ubsan_encode_value (op0), + ubsan_encode_value (op1)); + } + t = fold_build3 (COND_EXPR, void_type_node, t, tt, void_node); + + return t; +} + +/* Instrument left and right shifts. */ + +tree +ubsan_instrument_shift (location_t loc, enum tree_code code, + tree op0, tree op1) +{ + tree t, tt = NULL_TREE; + tree type0 = TREE_TYPE (op0); + tree type1 = TREE_TYPE (op1); + if (!INTEGRAL_TYPE_P (type0)) + return NULL_TREE; + + tree op1_utype = unsigned_type_for (type1); + HOST_WIDE_INT op0_prec = TYPE_PRECISION (type0); + tree uprecm1 = build_int_cst (op1_utype, op0_prec - 1); + + op0 = unshare_expr (op0); + op1 = unshare_expr (op1); + + t = fold_convert_loc (loc, op1_utype, op1); + t = fold_build2 (GT_EXPR, boolean_type_node, t, uprecm1); + + /* If this is not a signed operation, don't perform overflow checks. + Also punt on bit-fields. */ + if (TYPE_OVERFLOW_WRAPS (type0) + || GET_MODE_BITSIZE (TYPE_MODE (type0)) != TYPE_PRECISION (type0) + || !sanitize_flags_p (SANITIZE_SHIFT_BASE)) + ; + + /* For signed x << y, in C99/C11, the following: + (unsigned) x >> (uprecm1 - y) + if non-zero, is undefined. */ + else if (code == LSHIFT_EXPR && flag_isoc99 && cxx_dialect < cxx11) + { + tree x = fold_build2 (MINUS_EXPR, op1_utype, uprecm1, + fold_convert (op1_utype, unshare_expr (op1))); + tt = fold_convert_loc (loc, unsigned_type_for (type0), op0); + tt = fold_build2 (RSHIFT_EXPR, TREE_TYPE (tt), tt, x); + tt = fold_build2 (NE_EXPR, boolean_type_node, tt, + build_int_cst (TREE_TYPE (tt), 0)); + } + + /* For signed x << y, in C++11 and later, the following: + x < 0 || ((unsigned) x >> (uprecm1 - y)) + if > 1, is undefined. */ + else if (code == LSHIFT_EXPR && cxx_dialect >= cxx11) + { + tree x = fold_build2 (MINUS_EXPR, op1_utype, uprecm1, + fold_convert (op1_utype, unshare_expr (op1))); + tt = fold_convert_loc (loc, unsigned_type_for (type0), + unshare_expr (op0)); + tt = fold_build2 (RSHIFT_EXPR, TREE_TYPE (tt), tt, x); + tt = fold_build2 (GT_EXPR, boolean_type_node, tt, + build_int_cst (TREE_TYPE (tt), 1)); + x = fold_build2 (LT_EXPR, boolean_type_node, unshare_expr (op0), + build_int_cst (type0, 0)); + tt = fold_build2 (TRUTH_OR_EXPR, boolean_type_node, x, tt); + } + + /* If the condition was folded to 0, no need to instrument + this expression. */ + if (integer_zerop (t) && (tt == NULL_TREE || integer_zerop (tt))) + return NULL_TREE; + + /* In case we have a SAVE_EXPR in a conditional context, we need to + make sure it gets evaluated before the condition. */ + t = fold_build2 (COMPOUND_EXPR, TREE_TYPE (t), unshare_expr (op0), t); + t = fold_build2 (COMPOUND_EXPR, TREE_TYPE (t), unshare_expr (op1), t); + + enum sanitize_code recover_kind = SANITIZE_SHIFT_EXPONENT; + tree else_t = void_node; + if (tt) + { + if (!sanitize_flags_p (SANITIZE_SHIFT_EXPONENT)) + { + t = fold_build1 (TRUTH_NOT_EXPR, boolean_type_node, t); + t = fold_build2 (TRUTH_AND_EXPR, boolean_type_node, t, tt); + recover_kind = SANITIZE_SHIFT_BASE; + } + else + { + if (flag_sanitize_undefined_trap_on_error + || ((!(flag_sanitize_recover & SANITIZE_SHIFT_EXPONENT)) + == (!(flag_sanitize_recover & SANITIZE_SHIFT_BASE)))) + t = fold_build2 (TRUTH_OR_EXPR, boolean_type_node, t, tt); + else + else_t = tt; + } + } + + if (flag_sanitize_undefined_trap_on_error) + tt = build_call_expr_loc (loc, builtin_decl_explicit (BUILT_IN_TRAP), 0); + else + { + tree data = ubsan_create_data ("__ubsan_shift_data", 1, &loc, + ubsan_type_descriptor (type0), + ubsan_type_descriptor (type1), NULL_TREE, + NULL_TREE); + data = build_fold_addr_expr_loc (loc, data); + + enum built_in_function bcode + = (flag_sanitize_recover & recover_kind) + ? BUILT_IN_UBSAN_HANDLE_SHIFT_OUT_OF_BOUNDS + : BUILT_IN_UBSAN_HANDLE_SHIFT_OUT_OF_BOUNDS_ABORT; + tt = builtin_decl_explicit (bcode); + op0 = unshare_expr (op0); + op1 = unshare_expr (op1); + tt = build_call_expr_loc (loc, tt, 3, data, ubsan_encode_value (op0), + ubsan_encode_value (op1)); + if (else_t != void_node) + { + bcode = (flag_sanitize_recover & SANITIZE_SHIFT_BASE) + ? BUILT_IN_UBSAN_HANDLE_SHIFT_OUT_OF_BOUNDS + : BUILT_IN_UBSAN_HANDLE_SHIFT_OUT_OF_BOUNDS_ABORT; + tree else_tt = builtin_decl_explicit (bcode); + op0 = unshare_expr (op0); + op1 = unshare_expr (op1); + else_tt = build_call_expr_loc (loc, else_tt, 3, data, + ubsan_encode_value (op0), + ubsan_encode_value (op1)); + else_t = fold_build3 (COND_EXPR, void_type_node, else_t, + else_tt, void_node); + } + } + t = fold_build3 (COND_EXPR, void_type_node, t, tt, else_t); + + return t; +} + +/* Instrument variable length array bound. */ + +tree +ubsan_instrument_vla (location_t loc, tree size) +{ + tree type = TREE_TYPE (size); + tree t, tt; + + t = fold_build2 (LE_EXPR, boolean_type_node, size, build_int_cst (type, 0)); + if (flag_sanitize_undefined_trap_on_error) + tt = build_call_expr_loc (loc, builtin_decl_explicit (BUILT_IN_TRAP), 0); + else + { + tree data = ubsan_create_data ("__ubsan_vla_data", 1, &loc, + ubsan_type_descriptor (type), NULL_TREE, + NULL_TREE); + data = build_fold_addr_expr_loc (loc, data); + enum built_in_function bcode + = (flag_sanitize_recover & SANITIZE_VLA) + ? BUILT_IN_UBSAN_HANDLE_VLA_BOUND_NOT_POSITIVE + : BUILT_IN_UBSAN_HANDLE_VLA_BOUND_NOT_POSITIVE_ABORT; + tt = builtin_decl_explicit (bcode); + tt = build_call_expr_loc (loc, tt, 2, data, ubsan_encode_value (size)); + } + t = fold_build3 (COND_EXPR, void_type_node, t, tt, void_node); + + return t; +} + +/* Instrument missing return in C++ functions returning non-void. */ + +tree +ubsan_instrument_return (location_t loc) +{ + if (flag_sanitize_undefined_trap_on_error) + return build_call_expr_loc (loc, builtin_decl_explicit (BUILT_IN_TRAP), 0); + + tree data = ubsan_create_data ("__ubsan_missing_return_data", 1, &loc, + NULL_TREE, NULL_TREE); + tree t = builtin_decl_explicit (BUILT_IN_UBSAN_HANDLE_MISSING_RETURN); + return build_call_expr_loc (loc, t, 1, build_fold_addr_expr_loc (loc, data)); +} + +/* Instrument array bounds for ARRAY_REFs. We create special builtin, + that gets expanded in the sanopt pass, and make an array dimension + of it. ARRAY is the array, *INDEX is an index to the array. + Return NULL_TREE if no instrumentation is emitted. + IGNORE_OFF_BY_ONE is true if the ARRAY_REF is inside a ADDR_EXPR. */ + +tree +ubsan_instrument_bounds (location_t loc, tree array, tree *index, + bool ignore_off_by_one) +{ + tree type = TREE_TYPE (array); + tree domain = TYPE_DOMAIN (type); + + if (domain == NULL_TREE || TYPE_MAX_VALUE (domain) == NULL_TREE) + return NULL_TREE; + + tree bound = TYPE_MAX_VALUE (domain); + if (ignore_off_by_one) + bound = fold_build2 (PLUS_EXPR, TREE_TYPE (bound), bound, + build_int_cst (TREE_TYPE (bound), 1)); + + /* Detect flexible array members and suchlike, unless + -fsanitize=bounds-strict. */ + tree base = get_base_address (array); + if (!sanitize_flags_p (SANITIZE_BOUNDS_STRICT) + && TREE_CODE (array) == COMPONENT_REF + && base && (INDIRECT_REF_P (base) || TREE_CODE (base) == MEM_REF)) + { + tree next = NULL_TREE; + tree cref = array; + + /* Walk all structs/unions. */ + while (TREE_CODE (cref) == COMPONENT_REF) + { + if (TREE_CODE (TREE_TYPE (TREE_OPERAND (cref, 0))) == RECORD_TYPE) + for (next = DECL_CHAIN (TREE_OPERAND (cref, 1)); + next && TREE_CODE (next) != FIELD_DECL; + next = DECL_CHAIN (next)) + ; + if (next) + /* Not a last element. Instrument it. */ + break; + /* Ok, this is the last field of the structure/union. But the + aggregate containing the field must be the last field too, + recursively. */ + cref = TREE_OPERAND (cref, 0); + } + if (!next) + /* Don't instrument this flexible array member-like array in non-strict + -fsanitize=bounds mode. */ + return NULL_TREE; + } + + /* Don't emit instrumentation in the most common cases. */ + tree idx = NULL_TREE; + if (TREE_CODE (*index) == INTEGER_CST) + idx = *index; + else if (TREE_CODE (*index) == BIT_AND_EXPR + && TREE_CODE (TREE_OPERAND (*index, 1)) == INTEGER_CST) + idx = TREE_OPERAND (*index, 1); + if (idx + && TREE_CODE (bound) == INTEGER_CST + && tree_int_cst_sgn (idx) >= 0 + && tree_int_cst_le (idx, bound)) + return NULL_TREE; + + *index = save_expr (*index); + /* Create a "(T *) 0" tree node to describe the array type. */ + tree zero_with_type = build_int_cst (build_pointer_type (type), 0); + return build_call_expr_internal_loc (loc, IFN_UBSAN_BOUNDS, + void_type_node, 3, zero_with_type, + *index, bound); +} + +/* Return true iff T is an array that was instrumented by SANITIZE_BOUNDS. */ + +bool +ubsan_array_ref_instrumented_p (const_tree t) +{ + if (TREE_CODE (t) != ARRAY_REF) + return false; + + tree op1 = TREE_OPERAND (t, 1); + return TREE_CODE (op1) == COMPOUND_EXPR + && TREE_CODE (TREE_OPERAND (op1, 0)) == CALL_EXPR + && CALL_EXPR_FN (TREE_OPERAND (op1, 0)) == NULL_TREE + && CALL_EXPR_IFN (TREE_OPERAND (op1, 0)) == IFN_UBSAN_BOUNDS; +} + +/* Instrument an ARRAY_REF, if it hasn't already been instrumented. + IGNORE_OFF_BY_ONE is true if the ARRAY_REF is inside a ADDR_EXPR. */ + +void +ubsan_maybe_instrument_array_ref (tree *expr_p, bool ignore_off_by_one) +{ + if (!ubsan_array_ref_instrumented_p (*expr_p) + && sanitize_flags_p (SANITIZE_BOUNDS | SANITIZE_BOUNDS_STRICT) + && current_function_decl != NULL_TREE) + { + tree op0 = TREE_OPERAND (*expr_p, 0); + tree op1 = TREE_OPERAND (*expr_p, 1); + tree e = ubsan_instrument_bounds (EXPR_LOCATION (*expr_p), op0, &op1, + ignore_off_by_one); + if (e != NULL_TREE) + { + tree t = copy_node (*expr_p); + TREE_OPERAND (t, 1) = build2 (COMPOUND_EXPR, TREE_TYPE (op1), + e, op1); + *expr_p = t; + } + } +} + +static tree +ubsan_maybe_instrument_reference_or_call (location_t loc, tree op, tree ptype, + enum ubsan_null_ckind ckind) +{ + if (!sanitize_flags_p (SANITIZE_ALIGNMENT | SANITIZE_NULL) + || current_function_decl == NULL_TREE) + return NULL_TREE; + + tree type = TREE_TYPE (ptype); + tree orig_op = op; + bool instrument = false; + unsigned int mina = 0; + + if (sanitize_flags_p (SANITIZE_ALIGNMENT)) + { + mina = min_align_of_type (type); + if (mina <= 1) + mina = 0; + } + while ((TREE_CODE (op) == NOP_EXPR + || TREE_CODE (op) == NON_LVALUE_EXPR) + && TREE_CODE (TREE_TYPE (op)) == POINTER_TYPE) + op = TREE_OPERAND (op, 0); + if (TREE_CODE (op) == NOP_EXPR + && TREE_CODE (TREE_TYPE (op)) == REFERENCE_TYPE) + { + if (mina && mina > min_align_of_type (TREE_TYPE (TREE_TYPE (op)))) + instrument = true; + } + else + { + if (sanitize_flags_p (SANITIZE_NULL) && TREE_CODE (op) == ADDR_EXPR) + { + bool strict_overflow_p = false; + /* tree_single_nonzero_warnv_p will not return true for non-weak + non-automatic decls with -fno-delete-null-pointer-checks, + which is disabled during -fsanitize=null. We don't want to + instrument those, just weak vars though. */ + int save_flag_delete_null_pointer_checks + = flag_delete_null_pointer_checks; + flag_delete_null_pointer_checks = 1; + if (!tree_single_nonzero_warnv_p (op, &strict_overflow_p) + || strict_overflow_p) + instrument = true; + flag_delete_null_pointer_checks + = save_flag_delete_null_pointer_checks; + } + else if (sanitize_flags_p (SANITIZE_NULL)) + instrument = true; + if (mina && mina > 1) + { + if (!POINTER_TYPE_P (TREE_TYPE (op)) + || mina > get_pointer_alignment (op) / BITS_PER_UNIT) + instrument = true; + } + } + if (!instrument) + return NULL_TREE; + op = save_expr (orig_op); + gcc_assert (POINTER_TYPE_P (ptype)); + if (TREE_CODE (ptype) == REFERENCE_TYPE) + ptype = build_pointer_type (TREE_TYPE (ptype)); + tree kind = build_int_cst (ptype, ckind); + tree align = build_int_cst (pointer_sized_int_node, mina); + tree call + = build_call_expr_internal_loc (loc, IFN_UBSAN_NULL, void_type_node, + 3, op, kind, align); + TREE_SIDE_EFFECTS (call) = 1; + return fold_build2 (COMPOUND_EXPR, TREE_TYPE (op), call, op); +} + +/* Instrument a NOP_EXPR to REFERENCE_TYPE or INTEGER_CST with REFERENCE_TYPE + type if needed. */ + +void +ubsan_maybe_instrument_reference (tree *stmt_p) +{ + tree stmt = *stmt_p; + tree op = stmt; + if (TREE_CODE (stmt) == NOP_EXPR) + op = TREE_OPERAND (stmt, 0); + op = ubsan_maybe_instrument_reference_or_call (EXPR_LOCATION (stmt), op, + TREE_TYPE (stmt), + UBSAN_REF_BINDING); + if (op) + { + if (TREE_CODE (stmt) == NOP_EXPR) + TREE_OPERAND (stmt, 0) = op; + else + *stmt_p = op; + } +} + +/* Instrument a CALL_EXPR to a method if needed. */ + +void +ubsan_maybe_instrument_member_call (tree stmt, bool is_ctor) +{ + if (call_expr_nargs (stmt) == 0) + return; + tree op = CALL_EXPR_ARG (stmt, 0); + if (op == error_mark_node + || !POINTER_TYPE_P (TREE_TYPE (op))) + return; + op = ubsan_maybe_instrument_reference_or_call (EXPR_LOCATION (stmt), op, + TREE_TYPE (op), + is_ctor ? UBSAN_CTOR_CALL + : UBSAN_MEMBER_CALL); + if (op) + CALL_EXPR_ARG (stmt, 0) = op; +}