From d085c468179245fdd31c1014d3029ddd9e116e01 Mon Sep 17 00:00:00 2001 From: Bernd Schmidt Date: Wed, 18 Nov 2015 12:26:43 +0000 Subject: [PATCH] regrename.h (struct du_head): Add target_data_1 and target_data_2 fields. * regrename.h (struct du_head): Add target_data_1 and target_data_2 fields. * regrename.c (create_new_chain): Clear entire struct after allocating. * config/i386/i386.opt (mmitigate-rop): New option. * doc/invoke.texi (mmitigate-rop): Document. * config/i386/i386.c: Include "regrename.h". (ix86_rop_should_change_byte_p, reg_encoded_number, ix86_get_modrm_for_rop, set_rop_modrm_reg_bits, ix86_mitigate_rop): New static functions. (ix86_reorg): Call ix86_mitigate_rop if -fmitigate-rop. * config/i386/i386.md (attr "modrm_class"): New. (cmp_ccno_1, mov_xor, movstrict_xor, x86_movcc_0_m1. x86_movcc_0_m1_se) (x86_movcc_0_m1_neg): Override modrm_class attribute. From-SVN: r230543 --- gcc/ChangeLog | 18 +++ gcc/config/i386/i386.c | 316 +++++++++++++++++++++++++++++++++++++++ gcc/config/i386/i386.md | 19 +++ gcc/config/i386/i386.opt | 4 + gcc/doc/invoke.texi | 10 +- gcc/regrename.c | 5 +- gcc/regrename.h | 4 + 7 files changed, 371 insertions(+), 5 deletions(-) diff --git a/gcc/ChangeLog b/gcc/ChangeLog index 2fcf704497b..abf01d336f7 100644 --- a/gcc/ChangeLog +++ b/gcc/ChangeLog @@ -1,3 +1,21 @@ +2015-11-18 Bernd Schmidt + + * regrename.h (struct du_head): Add target_data_1 and target_data_2 + fields. + * regrename.c (create_new_chain): Clear entire struct after allocating. + + * config/i386/i386.opt (mmitigate-rop): New option. + * doc/invoke.texi (mmitigate-rop): Document. + * config/i386/i386.c: Include "regrename.h". + (ix86_rop_should_change_byte_p, reg_encoded_number, + ix86_get_modrm_for_rop, set_rop_modrm_reg_bits, ix86_mitigate_rop): New + static functions. + (ix86_reorg): Call ix86_mitigate_rop if -fmitigate-rop. + * config/i386/i386.md (attr "modrm_class"): New. + (cmp_ccno_1, mov_xor, movstrict_xor, + x86_movcc_0_m1. x86_movcc_0_m1_se) + (x86_movcc_0_m1_neg): Override modrm_class attribute. + 2015-11-18 Ilya Enkovich PR target/68405 diff --git a/gcc/config/i386/i386.c b/gcc/config/i386/i386.c index f1a287bafb0..83749d59f3e 100644 --- a/gcc/config/i386/i386.c +++ b/gcc/config/i386/i386.c @@ -74,6 +74,7 @@ along with GCC; see the file COPYING3. If not see #include "rtl-chkp.h" #include "dbgcnt.h" #include "case-cfn-macros.h" +#include "regrename.h" /* This file should be included last. */ #include "target-def.h" @@ -3974,6 +3975,15 @@ ix86_debug_options (void) return; } +/* Return true if T is one of the bytes we should avoid with + -fmitigate-rop. */ + +static bool +ix86_rop_should_change_byte_p (int t) +{ + return t == 0xc2 || t == 0xc3 || t == 0xca || t == 0xcb; +} + static const char *stringop_alg_names[] = { #define DEF_ENUM #define DEF_ALG(alg, name) #name, @@ -27303,6 +27313,100 @@ ix86_instantiate_decls (void) instantiate_decl_rtl (s->rtl); } +/* Return the number used for encoding REG, in the range 0..7. */ + +static int +reg_encoded_number (rtx reg) +{ + unsigned regno = REGNO (reg); + switch (regno) + { + case AX_REG: + return 0; + case CX_REG: + return 1; + case DX_REG: + return 2; + case BX_REG: + return 3; + case SP_REG: + return 4; + case BP_REG: + return 5; + case SI_REG: + return 6; + case DI_REG: + return 7; + default: + break; + } + if (IN_RANGE (regno, FIRST_STACK_REG, LAST_STACK_REG)) + return regno - FIRST_STACK_REG; + if (IN_RANGE (regno, FIRST_SSE_REG, LAST_SSE_REG)) + return regno - FIRST_SSE_REG; + if (IN_RANGE (regno, FIRST_MMX_REG, LAST_MMX_REG)) + return regno - FIRST_MMX_REG; + if (IN_RANGE (regno, FIRST_REX_SSE_REG, LAST_REX_SSE_REG)) + return regno - FIRST_REX_SSE_REG; + if (IN_RANGE (regno, FIRST_REX_INT_REG, LAST_REX_INT_REG)) + return regno - FIRST_REX_INT_REG; + if (IN_RANGE (regno, FIRST_MASK_REG, LAST_MASK_REG)) + return regno - FIRST_MASK_REG; + if (IN_RANGE (regno, FIRST_BND_REG, LAST_BND_REG)) + return regno - FIRST_BND_REG; + return -1; +} + +/* Given an insn INSN with NOPERANDS OPERANDS, return the modr/m byte used + in its encoding if it could be relevant for ROP mitigation, otherwise + return -1. If POPNO0 and POPNO1 are nonnull, store the operand numbers + used for calculating it into them. */ + +static int +ix86_get_modrm_for_rop (rtx_insn *insn, rtx *operands, int noperands, + int *popno0 = 0, int *popno1 = 0) +{ + if (asm_noperands (PATTERN (insn)) >= 0) + return -1; + int has_modrm = get_attr_modrm (insn); + if (!has_modrm) + return -1; + enum attr_modrm_class cls = get_attr_modrm_class (insn); + rtx op0, op1; + switch (cls) + { + case MODRM_CLASS_OP02: + gcc_assert (noperands >= 3); + if (popno0) + { + *popno0 = 0; + *popno1 = 2; + } + op0 = operands[0]; + op1 = operands[2]; + break; + case MODRM_CLASS_OP01: + gcc_assert (noperands >= 2); + if (popno0) + { + *popno0 = 0; + *popno1 = 1; + } + op0 = operands[0]; + op1 = operands[1]; + break; + default: + return -1; + } + if (REG_P (op0) && REG_P (op1)) + { + int enc0 = reg_encoded_number (op0); + int enc1 = reg_encoded_number (op1); + return 0xc0 + (enc1 << 3) + enc0; + } + return -1; +} + /* Check whether x86 address PARTS is a pc-relative address. */ static bool @@ -45098,6 +45202,215 @@ ix86_seh_fixup_eh_fallthru (void) } } +/* Given a register number BASE, the lowest of a group of registers, update + regsets IN and OUT with the registers that should be avoided in input + and output operands respectively when trying to avoid generating a modr/m + byte for -fmitigate-rop. */ + +static void +set_rop_modrm_reg_bits (int base, HARD_REG_SET &in, HARD_REG_SET &out) +{ + SET_HARD_REG_BIT (out, base); + SET_HARD_REG_BIT (out, base + 1); + SET_HARD_REG_BIT (in, base + 2); + SET_HARD_REG_BIT (in, base + 3); +} + +/* Called if -fmitigate_rop is in effect. Try to rewrite instructions so + that certain encodings of modr/m bytes do not occur. */ +static void +ix86_mitigate_rop (void) +{ + HARD_REG_SET input_risky; + HARD_REG_SET output_risky; + HARD_REG_SET inout_risky; + + CLEAR_HARD_REG_SET (output_risky); + CLEAR_HARD_REG_SET (input_risky); + SET_HARD_REG_BIT (output_risky, AX_REG); + SET_HARD_REG_BIT (output_risky, CX_REG); + SET_HARD_REG_BIT (input_risky, BX_REG); + SET_HARD_REG_BIT (input_risky, DX_REG); + set_rop_modrm_reg_bits (FIRST_SSE_REG, input_risky, output_risky); + set_rop_modrm_reg_bits (FIRST_REX_INT_REG, input_risky, output_risky); + set_rop_modrm_reg_bits (FIRST_REX_SSE_REG, input_risky, output_risky); + set_rop_modrm_reg_bits (FIRST_EXT_REX_SSE_REG, input_risky, output_risky); + set_rop_modrm_reg_bits (FIRST_MASK_REG, input_risky, output_risky); + set_rop_modrm_reg_bits (FIRST_BND_REG, input_risky, output_risky); + COPY_HARD_REG_SET (inout_risky, input_risky); + IOR_HARD_REG_SET (inout_risky, output_risky); + + compute_bb_for_insn (); + df_note_add_problem (); + df_analyze (); + + regrename_init (true); + regrename_analyze (NULL); + + auto_vec cands; + + for (rtx_insn *insn = get_insns (); insn; insn = NEXT_INSN (insn)) + { + if (!NONDEBUG_INSN_P (insn)) + continue; + + if (GET_CODE (PATTERN (insn)) == USE + || GET_CODE (PATTERN (insn)) == CLOBBER) + continue; + + extract_insn (insn); + + int opno0, opno1; + int modrm = ix86_get_modrm_for_rop (insn, recog_data.operand, + recog_data.n_operands, &opno0, + &opno1); + + if (!ix86_rop_should_change_byte_p (modrm)) + continue; + + insn_rr_info *info = &insn_rr[INSN_UID (insn)]; + + /* This happens when regrename has to fail a block. */ + if (!info->op_info) + continue; + + if (info->op_info[opno0].n_chains != 0) + { + gcc_assert (info->op_info[opno0].n_chains == 1); + du_head_p op0c; + op0c = regrename_chain_from_id (info->op_info[opno0].heads[0]->id); + if (op0c->target_data_1 + op0c->target_data_2 == 0 + && !op0c->cannot_rename) + cands.safe_push (op0c); + + op0c->target_data_1++; + } + if (info->op_info[opno1].n_chains != 0) + { + gcc_assert (info->op_info[opno1].n_chains == 1); + du_head_p op1c; + op1c = regrename_chain_from_id (info->op_info[opno1].heads[0]->id); + if (op1c->target_data_1 + op1c->target_data_2 == 0 + && !op1c->cannot_rename) + cands.safe_push (op1c); + + op1c->target_data_2++; + } + } + + int i; + du_head_p head; + FOR_EACH_VEC_ELT (cands, i, head) + { + int old_reg, best_reg; + HARD_REG_SET unavailable; + + CLEAR_HARD_REG_SET (unavailable); + if (head->target_data_1) + IOR_HARD_REG_SET (unavailable, output_risky); + if (head->target_data_2) + IOR_HARD_REG_SET (unavailable, input_risky); + + int n_uses; + reg_class superclass = regrename_find_superclass (head, &n_uses, + &unavailable); + old_reg = head->regno; + best_reg = find_rename_reg (head, superclass, &unavailable, + old_reg, false); + bool ok = regrename_do_replace (head, best_reg); + gcc_assert (ok); + if (dump_file) + fprintf (dump_file, "Chain %d renamed as %s in %s\n", head->id, + reg_names[best_reg], reg_class_names[superclass]); + + } + + regrename_finish (); + + df_analyze (); + + basic_block bb; + regset_head live; + + INIT_REG_SET (&live); + + FOR_EACH_BB_FN (bb, cfun) + { + rtx_insn *insn; + + COPY_REG_SET (&live, DF_LR_OUT (bb)); + df_simulate_initialize_backwards (bb, &live); + + FOR_BB_INSNS_REVERSE (bb, insn) + { + if (!NONDEBUG_INSN_P (insn)) + continue; + + df_simulate_one_insn_backwards (bb, insn, &live); + + if (GET_CODE (PATTERN (insn)) == USE + || GET_CODE (PATTERN (insn)) == CLOBBER) + continue; + + extract_insn (insn); + constrain_operands_cached (insn, reload_completed); + int opno0, opno1; + int modrm = ix86_get_modrm_for_rop (insn, recog_data.operand, + recog_data.n_operands, &opno0, + &opno1); + if (modrm < 0 + || !ix86_rop_should_change_byte_p (modrm) + || opno0 == opno1) + continue; + + rtx oldreg = recog_data.operand[opno1]; + preprocess_constraints (insn); + const operand_alternative *alt = which_op_alt (); + + int i; + for (i = 0; i < recog_data.n_operands; i++) + if (i != opno1 + && alt[i].earlyclobber + && reg_overlap_mentioned_p (recog_data.operand[i], + oldreg)) + break; + + if (i < recog_data.n_operands) + continue; + + if (dump_file) + fprintf (dump_file, + "attempting to fix modrm byte in insn %d:" + " reg %d class %s", INSN_UID (insn), REGNO (oldreg), + reg_class_names[alt[opno1].cl]); + + HARD_REG_SET unavailable; + REG_SET_TO_HARD_REG_SET (unavailable, &live); + SET_HARD_REG_BIT (unavailable, REGNO (oldreg)); + IOR_COMPL_HARD_REG_SET (unavailable, call_used_reg_set); + IOR_HARD_REG_SET (unavailable, fixed_reg_set); + IOR_HARD_REG_SET (unavailable, output_risky); + IOR_COMPL_HARD_REG_SET (unavailable, + reg_class_contents[alt[opno1].cl]); + + for (i = 0; i < FIRST_PSEUDO_REGISTER; i++) + if (!TEST_HARD_REG_BIT (unavailable, i)) + break; + if (i == FIRST_PSEUDO_REGISTER) + { + if (dump_file) + fprintf (dump_file, ", none available\n"); + continue; + } + if (dump_file) + fprintf (dump_file, " -> %d\n", i); + rtx newreg = gen_rtx_REG (recog_data.operand_mode[opno1], i); + validate_change (insn, recog_data.operand_loc[opno1], newreg, false); + insn = emit_insn_before (gen_move_insn (newreg, oldreg), insn); + } + } +} + /* Implement machine specific optimizations. We implement padding of returns for K8 CPUs and pass to avoid 4 jumps in the single 16 byte window. */ static void @@ -45107,6 +45420,9 @@ ix86_reorg (void) with old MDEP_REORGS that are not CFG based. Recompute it now. */ compute_bb_for_insn (); + if (flag_mitigate_rop) + ix86_mitigate_rop (); + if (TARGET_SEH && current_function_has_exception_handlers ()) ix86_seh_fixup_eh_fallthru (); diff --git a/gcc/config/i386/i386.md b/gcc/config/i386/i386.md index 34a6d3f4d82..39b058f82c7 100644 --- a/gcc/config/i386/i386.md +++ b/gcc/config/i386/i386.md @@ -637,6 +637,19 @@ ] (const_int 1))) +(define_attr "modrm_class" "none,incdec,op0,op01,op02,pushpop,unknown" + (cond [(eq_attr "modrm" "0") + (const_string "none") + (eq_attr "type" "alu,imul,ishift") + (const_string "op02") + (eq_attr "type" "imov,imovx,lea,alu1,icmp") + (const_string "op01") + (eq_attr "type" "incdec") + (const_string "incdec") + (eq_attr "type" "push,pop") + (const_string "pushpop")] + (const_string "unknown"))) + ;; The (bounding maximum) length of an instruction in bytes. ;; ??? fistp and frndint are in fact fldcw/{fistp,frndint}/fldcw sequences. ;; Later we may want to split them and compute proper length as for @@ -1235,6 +1248,7 @@ cmp{}\t{%1, %0|%0, %1}" [(set_attr "type" "test,icmp") (set_attr "length_immediate" "0,1") + (set_attr "modrm_class" "op0,unknown") (set_attr "mode" "")]) (define_insn "*cmp_1" @@ -1942,6 +1956,7 @@ "reload_completed" "xor{l}\t%k0, %k0" [(set_attr "type" "alu1") + (set_attr "modrm_class" "op0") (set_attr "mode" "SI") (set_attr "length_immediate" "0")]) @@ -2713,6 +2728,7 @@ "reload_completed" "xor{}\t%0, %0" [(set_attr "type" "alu1") + (set_attr "modrm_class" "op0") (set_attr "mode" "") (set_attr "length_immediate" "0")]) @@ -16847,6 +16863,7 @@ ; Since we don't have the proper number of operands for an alu insn, ; fill in all the blanks. [(set_attr "type" "alu") + (set_attr "modrm_class" "op0") (set_attr "use_carry" "1") (set_attr "pent_pair" "pu") (set_attr "memory" "none") @@ -16864,6 +16881,7 @@ "" "sbb{}\t%0, %0" [(set_attr "type" "alu") + (set_attr "modrm_class" "op0") (set_attr "use_carry" "1") (set_attr "pent_pair" "pu") (set_attr "memory" "none") @@ -16879,6 +16897,7 @@ "" "sbb{}\t%0, %0" [(set_attr "type" "alu") + (set_attr "modrm_class" "op0") (set_attr "use_carry" "1") (set_attr "pent_pair" "pu") (set_attr "memory" "none") diff --git a/gcc/config/i386/i386.opt b/gcc/config/i386/i386.opt index 82de21f7881..2723c22213b 100644 --- a/gcc/config/i386/i386.opt +++ b/gcc/config/i386/i386.opt @@ -889,3 +889,7 @@ Enum(stack_protector_guard) String(tls) Value(SSP_TLS) EnumValue Enum(stack_protector_guard) String(global) Value(SSP_GLOBAL) + +mmitigate-rop +Target Var(flag_mitigate_rop) Init(0) +Attempt to avoid generating instruction sequences containing ret bytes. diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index 8be39b7fa64..7cef176fdc9 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -1116,7 +1116,8 @@ See RS/6000 and PowerPC Options. -m32 -m64 -mx32 -m16 -miamcu -mlarge-data-threshold=@var{num} @gol -msse2avx -mfentry -mrecord-mcount -mnop-mcount -m8bit-idiv @gol -mavx256-split-unaligned-load -mavx256-split-unaligned-store @gol --malign-data=@var{type} -mstack-protector-guard=@var{guard}} +-malign-data=@var{type} -mstack-protector-guard=@var{guard} @gol +-mmitigate-rop} @emph{x86 Windows Options} @gccoptlist{-mconsole -mcygwin -mno-cygwin -mdll @gol @@ -23708,6 +23709,13 @@ locations are @samp{global} for global canary or @samp{tls} for per-thread canary in the TLS block (the default). This option has effect only when @option{-fstack-protector} or @option{-fstack-protector-all} is specified. +@item -mmitigate-rop +@opindex mmitigate-rop +Try to avoid generating code sequences that contain unintended return +opcodes, to mitigate against certain forms of attack. At the moment, +this option is limited in what it can do and should not be relied +on to provide serious protection. + @end table These @samp{-m} switches are supported in addition to the above diff --git a/gcc/regrename.c b/gcc/regrename.c index 1f11695c5db..e2a1e83c7ea 100644 --- a/gcc/regrename.c +++ b/gcc/regrename.c @@ -227,13 +227,10 @@ create_new_chain (unsigned this_regno, unsigned this_nregs, rtx *loc, struct du_chain *this_du; int nregs; + memset (head, 0, sizeof *head); head->next_chain = open_chains; head->regno = this_regno; head->nregs = this_nregs; - head->need_caller_save_reg = 0; - head->cannot_rename = 0; - head->renamed = 0; - head->tied_chain = NULL; id_to_chain.safe_push (head); head->id = current_id++; diff --git a/gcc/regrename.h b/gcc/regrename.h index c702835cfe2..6fdaff1df24 100644 --- a/gcc/regrename.h +++ b/gcc/regrename.h @@ -49,6 +49,10 @@ struct du_head unsigned int cannot_rename:1; /* Nonzero if the chain has already been renamed. */ unsigned int renamed:1; + + /* Fields for use by target code. */ + unsigned int target_data_1; + unsigned int target_data_2; }; typedef struct du_head *du_head_p; -- 2.30.2