'Retpoline' mitigation technique for Spectre (branch target injection) [CVE-2017-5715]: https://security.googleblog.com/2018/01/more-details-about-mitigations-for-cpu_4.html https://support.google.com/faqs/answer/7625886 https://spectreattack.com/ https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-5715 Patch copied from the 'retpoline-20180107' branch of upstream source repository (please add new / update existing patches when new 'retpoline-xxxxxxxx' branch appears): http://git.infradead.org/users/dwmw2/gcc-retpoline.git From fb8875abab630962dbcb08c822b1b960fa5a51d4 Mon Sep 17 00:00:00 2001 From: "H.J. Lu" Date: Wed, 19 Jul 2017 19:23:02 -0700 Subject: [PATCH 13/17] Add -mindirect-branch-register and tests Add -mindirect-branch-register to force indirect branch via register. This is implemented by disabling patterns of indirect branch via memory, similar to TARGET_X32. With -mindirect-branch-register: void (*func) (void); void bar (void) { func (); } is compiled into: movq func(%rip), %rax jmp __x86.indirect_thunk.ax __x86.indirect_thunk.ax: call .LIND3 .LIND2: lfence jmp .LIND2 .LIND3: mov %rax, (%rsp) ret and void (*func) (void); int bar (void) { func (); return 0; } is compiled into: subq $8, %rsp movq func(%rip), %rax call __x86.indirect_thunk.ax xorl %eax, %eax addq $8, %rsp ret * config/i386/constraints.md (Bs): Disallow memory operand for -mindirect-branch-register. (Bw): Likewise. * config/i386/predicates.md (indirect_branch_operand): Likewise. (GOT_memory_operand): Likewise. (call_insn_operand): Likewise. (sibcall_insn_operand): Likewise. (GOT32_symbol_operand): Likewise. * config/i386/i386.md (indirect_jump): Call convert_memory_address for -mindirect-branch-register. (tablejump): Likewise. (*sibcall_memory): Likewise. (*sibcall_value_memory): Likewise. Disallow peepholes of indirect call and jump via memory for -mindirect-branch-register. (*call_pop): Replace m with Bw. (*call_value_pop): Likewise. (*sibcall_pop_memory): Replace m with Bs. --- gcc/config/i386/constraints.md | 12 +++++--- gcc/config/i386/i386.md | 34 ++++++++++++++-------- gcc/config/i386/i386.opt | 4 +++ gcc/config/i386/predicates.md | 21 ++++++++----- .../gcc.target/i386/indirect-thunk-register-1.c | 22 ++++++++++++++ .../gcc.target/i386/indirect-thunk-register-2.c | 20 +++++++++++++ .../gcc.target/i386/indirect-thunk-register-3.c | 19 ++++++++++++ 7 files changed, 109 insertions(+), 23 deletions(-) create mode 100644 gcc/testsuite/gcc.target/i386/indirect-thunk-register-1.c create mode 100644 gcc/testsuite/gcc.target/i386/indirect-thunk-register-2.c create mode 100644 gcc/testsuite/gcc.target/i386/indirect-thunk-register-3.c diff --git a/gcc/config/i386/constraints.md b/gcc/config/i386/constraints.md index 38d604fdace..697caf704dd 100644 --- a/gcc/config/i386/constraints.md +++ b/gcc/config/i386/constraints.md @@ -198,16 +198,20 @@ (define_constraint "Bs" "@internal Sibcall memory operand." - (ior (and (not (match_test "TARGET_X32")) + (ior (and (not (match_test "TARGET_X32 + || ix86_indirect_branch_thunk_register")) (match_operand 0 "sibcall_memory_operand")) - (and (match_test "TARGET_X32 && Pmode == DImode") + (and (match_test "TARGET_X32 && Pmode == DImode + && !ix86_indirect_branch_thunk_register") (match_operand 0 "GOT_memory_operand")))) (define_constraint "Bw" "@internal Call memory operand." - (ior (and (not (match_test "TARGET_X32")) + (ior (and (not (match_test "TARGET_X32 + || ix86_indirect_branch_thunk_register")) (match_operand 0 "memory_operand")) - (and (match_test "TARGET_X32 && Pmode == DImode") + (and (match_test "TARGET_X32 && Pmode == DImode + && !ix86_indirect_branch_thunk_register") (match_operand 0 "GOT_memory_operand")))) (define_constraint "Bz" diff --git a/gcc/config/i386/i386.md b/gcc/config/i386/i386.md index 00a9afef225..473fa5c089b 100644 --- a/gcc/config/i386/i386.md +++ b/gcc/config/i386/i386.md @@ -11608,7 +11608,7 @@ [(set (pc) (match_operand 0 "indirect_branch_operand"))] "" { - if (TARGET_X32) + if (TARGET_X32 || ix86_indirect_branch_thunk_register) operands[0] = convert_memory_address (word_mode, operands[0]); }) @@ -11657,7 +11657,7 @@ OPTAB_DIRECT); } - if (TARGET_X32) + if (TARGET_X32 || ix86_indirect_branch_thunk_register) operands[0] = convert_memory_address (word_mode, operands[0]); }) @@ -11844,7 +11844,7 @@ [(call (mem:QI (match_operand:W 0 "memory_operand" "m")) (match_operand 1)) (unspec [(const_int 0)] UNSPEC_PEEPSIB)] - "!TARGET_X32" + "!TARGET_X32 && !ix86_indirect_branch_thunk_register" "* return ix86_output_call_insn (insn, operands[0]);" [(set_attr "type" "call")]) @@ -11853,7 +11853,9 @@ (match_operand:W 1 "memory_operand")) (call (mem:QI (match_dup 0)) (match_operand 3))] - "!TARGET_X32 && SIBLING_CALL_P (peep2_next_insn (1)) + "!TARGET_X32 + && !ix86_indirect_branch_thunk_register + && SIBLING_CALL_P (peep2_next_insn (1)) && !reg_mentioned_p (operands[0], CALL_INSN_FUNCTION_USAGE (peep2_next_insn (1)))" [(parallel [(call (mem:QI (match_dup 1)) @@ -11866,7 +11868,9 @@ (unspec_volatile [(const_int 0)] UNSPECV_BLOCKAGE) (call (mem:QI (match_dup 0)) (match_operand 3))] - "!TARGET_X32 && SIBLING_CALL_P (peep2_next_insn (2)) + "!TARGET_X32 + && !ix86_indirect_branch_thunk_register + && SIBLING_CALL_P (peep2_next_insn (2)) && !reg_mentioned_p (operands[0], CALL_INSN_FUNCTION_USAGE (peep2_next_insn (2)))" [(unspec_volatile [(const_int 0)] UNSPECV_BLOCKAGE) @@ -11888,7 +11892,7 @@ }) (define_insn "*call_pop" - [(call (mem:QI (match_operand:SI 0 "call_insn_operand" "lmBz")) + [(call (mem:QI (match_operand:SI 0 "call_insn_operand" "lBwBz")) (match_operand 1)) (set (reg:SI SP_REG) (plus:SI (reg:SI SP_REG) @@ -11908,7 +11912,7 @@ [(set_attr "type" "call")]) (define_insn "*sibcall_pop_memory" - [(call (mem:QI (match_operand:SI 0 "memory_operand" "m")) + [(call (mem:QI (match_operand:SI 0 "memory_operand" "Bs")) (match_operand 1)) (set (reg:SI SP_REG) (plus:SI (reg:SI SP_REG) @@ -11962,7 +11966,9 @@ [(set (match_operand:W 0 "register_operand") (match_operand:W 1 "memory_operand")) (set (pc) (match_dup 0))] - "!TARGET_X32 && peep2_reg_dead_p (2, operands[0])" + "!TARGET_X32 + && !ix86_indirect_branch_thunk_register + && peep2_reg_dead_p (2, operands[0])" [(set (pc) (match_dup 1))]) ;; Call subroutine, returning value in operand 0 @@ -12043,7 +12049,7 @@ (call (mem:QI (match_operand:W 1 "memory_operand" "m")) (match_operand 2))) (unspec [(const_int 0)] UNSPEC_PEEPSIB)] - "!TARGET_X32" + "!TARGET_X32 && !ix86_indirect_branch_thunk_register" "* return ix86_output_call_insn (insn, operands[1]);" [(set_attr "type" "callv")]) @@ -12053,7 +12059,9 @@ (set (match_operand 2) (call (mem:QI (match_dup 0)) (match_operand 3)))] - "!TARGET_X32 && SIBLING_CALL_P (peep2_next_insn (1)) + "!TARGET_X32 + && !ix86_indirect_branch_thunk_register + && SIBLING_CALL_P (peep2_next_insn (1)) && !reg_mentioned_p (operands[0], CALL_INSN_FUNCTION_USAGE (peep2_next_insn (1)))" [(parallel [(set (match_dup 2) @@ -12068,7 +12076,9 @@ (set (match_operand 2) (call (mem:QI (match_dup 0)) (match_operand 3)))] - "!TARGET_X32 && SIBLING_CALL_P (peep2_next_insn (2)) + "!TARGET_X32 + && !ix86_indirect_branch_thunk_register + && SIBLING_CALL_P (peep2_next_insn (2)) && !reg_mentioned_p (operands[0], CALL_INSN_FUNCTION_USAGE (peep2_next_insn (2)))" [(unspec_volatile [(const_int 0)] UNSPECV_BLOCKAGE) @@ -12093,7 +12103,7 @@ (define_insn "*call_value_pop" [(set (match_operand 0) - (call (mem:QI (match_operand:SI 1 "call_insn_operand" "lmBz")) + (call (mem:QI (match_operand:SI 1 "call_insn_operand" "lBwBz")) (match_operand 2))) (set (reg:SI SP_REG) (plus:SI (reg:SI SP_REG) diff --git a/gcc/config/i386/i386.opt b/gcc/config/i386/i386.opt index fc2c81c3fb5..802245f4efe 100644 --- a/gcc/config/i386/i386.opt +++ b/gcc/config/i386/i386.opt @@ -952,6 +952,10 @@ Enum(indirect_branch) String(thunk-inline) Value(indirect_branch_thunk_inline) EnumValue Enum(indirect_branch) String(thunk-extern) Value(indirect_branch_thunk_extern) +mindirect-branch-register +Target Report Var(ix86_indirect_branch_thunk_register) Init(0) +Force indirect call and jump via register. + mindirect-branch-loop= Target Report RejectNegative Joined Enum(indirect_branch_loop) Var(ix86_indirect_branch_loop) Undocumented Init(indirect_branch_loop_lfence) diff --git a/gcc/config/i386/predicates.md b/gcc/config/i386/predicates.md index 8f250a2e720..fc4933e4533 100644 --- a/gcc/config/i386/predicates.md +++ b/gcc/config/i386/predicates.md @@ -635,7 +635,8 @@ ;; Test for a valid operand for indirect branch. (define_predicate "indirect_branch_operand" (ior (match_operand 0 "register_operand") - (and (not (match_test "TARGET_X32")) + (and (not (match_test "TARGET_X32 + || ix86_indirect_branch_thunk_register")) (match_operand 0 "memory_operand")))) ;; Return true if OP is a memory operands that can be used in sibcalls. @@ -664,7 +665,8 @@ ;; Return true if OP is a GOT memory operand. (define_predicate "GOT_memory_operand" - (match_operand 0 "memory_operand") + (and (match_test "!ix86_indirect_branch_thunk_register") + (match_operand 0 "memory_operand")) { op = XEXP (op, 0); return (GET_CODE (op) == CONST @@ -678,9 +680,11 @@ (ior (match_test "constant_call_address_operand (op, mode == VOIDmode ? mode : Pmode)") (match_operand 0 "call_register_no_elim_operand") - (ior (and (not (match_test "TARGET_X32")) + (ior (and (not (match_test "TARGET_X32 + || ix86_indirect_branch_thunk_register")) (match_operand 0 "memory_operand")) - (and (match_test "TARGET_X32 && Pmode == DImode") + (and (match_test "TARGET_X32 && Pmode == DImode + && !ix86_indirect_branch_thunk_register") (match_operand 0 "GOT_memory_operand"))))) ;; Similarly, but for tail calls, in which we cannot allow memory references. @@ -688,14 +692,17 @@ (ior (match_test "constant_call_address_operand (op, mode == VOIDmode ? mode : Pmode)") (match_operand 0 "register_no_elim_operand") - (ior (and (not (match_test "TARGET_X32")) + (ior (and (not (match_test "TARGET_X32 + || ix86_indirect_branch_thunk_register")) (match_operand 0 "sibcall_memory_operand")) - (and (match_test "TARGET_X32 && Pmode == DImode") + (and (match_test "TARGET_X32 && Pmode == DImode + && !ix86_indirect_branch_thunk_register") (match_operand 0 "GOT_memory_operand"))))) ;; Return true if OP is a 32-bit GOT symbol operand. (define_predicate "GOT32_symbol_operand" - (match_test "GET_CODE (op) == CONST + (match_test "!ix86_indirect_branch_thunk_register + && GET_CODE (op) == CONST && GET_CODE (XEXP (op, 0)) == UNSPEC && XINT (XEXP (op, 0), 1) == UNSPEC_GOT")) diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-register-1.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-register-1.c new file mode 100644 index 00000000000..ef493a05bbf --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-register-1.c @@ -0,0 +1,22 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -mindirect-branch=thunk -mindirect-branch-register -fno-pic" } */ + +typedef void (*dispatch_t)(long offset); + +dispatch_t dispatch; + +void +male_indirect_jump (long offset) +{ + dispatch(offset); +} + +/* { dg-final { scan-assembler "jmp\[ \t\]*__x86.indirect_thunk\.(r|e)ax" } } */ +/* { dg-final { scan-assembler "jmp\[ \t\]*\.LIND" } } */ +/* { dg-final { scan-assembler "call\[ \t\]*\.LIND" } } */ +/* { dg-final { scan-assembler "mov\[ \t\](%eax|%rax), \\((%esp|%rsp)\\)" } } */ +/* { dg-final { scan-assembler {\tlfence} } } */ +/* { dg-final { scan-assembler-not "push(?:l|q)\[ \t\]*_?dispatch" } } */ +/* { dg-final { scan-assembler-not "pushq\[ \t\]%rax" } } */ +/* { dg-final { scan-assembler-not "__x86.indirect_thunk\n" } } */ +/* { dg-final { scan-assembler-not "__x86.indirect_thunk_bnd\n" } } */ diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-register-2.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-register-2.c new file mode 100644 index 00000000000..89fc8e6e6c4 --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-register-2.c @@ -0,0 +1,20 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -mindirect-branch=thunk-inline -mindirect-branch-register -fno-pic" } */ + +typedef void (*dispatch_t)(long offset); + +dispatch_t dispatch; + +void +male_indirect_jump (long offset) +{ + dispatch(offset); +} + +/* { dg-final { scan-assembler "jmp\[ \t\]*\.LIND" } } */ +/* { dg-final { scan-assembler "call\[ \t\]*\.LIND" } } */ +/* { dg-final { scan-assembler "mov\[ \t\](%eax|%rax), \\((%esp|%rsp)\\)" } } */ +/* { dg-final { scan-assembler {\tlfence} } } */ +/* { dg-final { scan-assembler-not "push(?:l|q)\[ \t\]*_?dispatch" } } */ +/* { dg-final { scan-assembler-not "pushq\[ \t\]%rax" } } */ +/* { dg-final { scan-assembler-not "__x86.indirect_thunk" } } */ diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-register-3.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-register-3.c new file mode 100644 index 00000000000..31af7ac05b8 --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-register-3.c @@ -0,0 +1,19 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -mindirect-branch=thunk-extern -mindirect-branch-register -fno-pic" } */ + +typedef void (*dispatch_t)(long offset); + +dispatch_t dispatch; + +void +male_indirect_jump (long offset) +{ + dispatch(offset); +} + +/* { dg-final { scan-assembler "jmp\[ \t\]*__x86.indirect_thunk\.(r|e)ax" } } */ +/* { dg-final { scan-assembler-not "push(?:l|q)\[ \t\]*_?dispatch" } } */ +/* { dg-final { scan-assembler-not "pushq\[ \t\]%rax" } } */ +/* { dg-final { scan-assembler-not {\t(lfence|pause|nop)} } } */ +/* { dg-final { scan-assembler-not "jmp\[ \t\]*\.LIND" } } */ +/* { dg-final { scan-assembler-not "call\[ \t\]*\.LIND" } } */ -- 2.15.1