aarch64: Introduce SLS mitigation for RET and BR instructions
authorMatthew Malcomson <matthew.malcomson@arm.com>
Thu, 9 Jul 2020 08:11:59 +0000 (09:11 +0100)
committerMatthew Malcomson <matthew.malcomson@arm.com>
Thu, 9 Jul 2020 08:18:16 +0000 (09:18 +0100)
Instructions following RET or BR are not necessarily executed.  In order
to avoid speculation past RET and BR we can simply append a speculation
barrier.

Since these speculation barriers will not be architecturally executed,
they are not expected to add a high performance penalty.

The speculation barrier is to be SB when targeting architectures which
have this enabled, and DSB SY + ISB otherwise.

We add tests for each of the cases where such an instruction was seen.

This is implemented by modifying each machine description pattern that
emits either a RET or a BR instruction.  We choose not to use something
like `TARGET_ASM_FUNCTION_EPILOGUE` since it does not affect the
`indirect_jump`, `jump`, `sibcall_insn` and `sibcall_value_insn`
patterns and we find it preferable to implement the functionality in the
same way for every pattern.

There is one particular case which is slightly tricky.  The
implementation of TARGET_ASM_TRAMPOLINE_TEMPLATE uses a BR which needs
to be mitigated against.  The trampoline template is used *once* per
compilation unit, and the TRAMPOLINE_SIZE is exposed to the user via the
builtin macro __LIBGCC_TRAMPOLINE_SIZE__.
In the future we may implement function specific attributes to turn on
and off hardening on a per-function basis.
The fixed nature of the trampoline described above implies it will be
safer to ensure this speculation barrier is always used.

Testing:
  Bootstrap and regtest done on aarch64-none-linux
  Used a temporary hack(1) to use these options on every test in the
  testsuite and a script to check that the output never emitted an
  unmitigated RET or BR.

1) Temporary hack was a change to the testsuite to always use
`-save-temps` and run a script on the assembly output of those
compilations which produced one to ensure every RET or BR is immediately
followed by a speculation barrier.

gcc/ChangeLog:

* config/aarch64/aarch64-protos.h (aarch64_sls_barrier): New.
* config/aarch64/aarch64.c (aarch64_output_casesi): Emit
speculation barrier after BR instruction if needs be.
(aarch64_trampoline_init): Handle ptr_mode value & adjust size
of code copied.
(aarch64_sls_barrier): New.
(aarch64_asm_trampoline_template): Add needed barriers.
* config/aarch64/aarch64.h (AARCH64_ISA_SB): New.
(TARGET_SB): New.
(TRAMPOLINE_SIZE): Account for barrier.
* config/aarch64/aarch64.md (indirect_jump, *casesi_dispatch,
simple_return, *do_return, *sibcall_insn, *sibcall_value_insn):
Emit barrier if needs be, also account for possible barrier using
"sls_length" attribute.
(sls_length): New attribute.
(length): Determine default using any non-default sls_length
value.

gcc/testsuite/ChangeLog:

* gcc.target/aarch64/sls-mitigation/sls-miti-retbr.c: New test.
* gcc.target/aarch64/sls-mitigation/sls-miti-retbr-pacret.c:
New test.
* gcc.target/aarch64/sls-mitigation/sls-mitigation.exp: New file.
* lib/target-supports.exp (check_effective_target_aarch64_asm_sb_ok):
New proc.

gcc/config/aarch64/aarch64-protos.h
gcc/config/aarch64/aarch64.c
gcc/config/aarch64/aarch64.h
gcc/config/aarch64/aarch64.md
gcc/testsuite/gcc.target/aarch64/sls-mitigation/sls-miti-retbr-pacret.c [new file with mode: 0644]
gcc/testsuite/gcc.target/aarch64/sls-mitigation/sls-miti-retbr.c [new file with mode: 0644]
gcc/testsuite/gcc.target/aarch64/sls-mitigation/sls-mitigation.exp [new file with mode: 0644]
gcc/testsuite/lib/target-supports.exp

index 8ca67d7e69edaf73c84f079e7e1c483009ad10c0..b035e4ec78e2ef1c9a931148dffacf6a50345b84 100644 (file)
@@ -780,6 +780,7 @@ extern const atomic_ool_names aarch64_ool_ldeor_names;
 
 tree aarch64_resolve_overloaded_builtin_general (location_t, tree, void *);
 
+const char *aarch64_sls_barrier (int);
 extern bool aarch64_harden_sls_retbr_p (void);
 extern bool aarch64_harden_sls_blr_p (void);
 
index b407b13fb1bdea89f41eba2b882b1ef6ff9181b3..863d18849ade59dc725025e6a237faa37356f936 100644 (file)
@@ -10843,16 +10843,27 @@ aarch64_asm_trampoline_template (FILE *f)
 
   if (TARGET_ILP32)
     {
-      asm_fprintf (f, "\tldr\tw%d, .+12\n", IP1_REGNUM - R0_REGNUM);
-      asm_fprintf (f, "\tldr\tw%d, .+12\n", STATIC_CHAIN_REGNUM - R0_REGNUM);
+      asm_fprintf (f, "\tldr\tw%d, .+20\n", IP1_REGNUM - R0_REGNUM);
+      asm_fprintf (f, "\tldr\tw%d, .+20\n", STATIC_CHAIN_REGNUM - R0_REGNUM);
     }
   else
     {
-      asm_fprintf (f, "\tldr\t%s, .+12\n", reg_names [IP1_REGNUM]);
-      asm_fprintf (f, "\tldr\t%s, .+16\n", reg_names [STATIC_CHAIN_REGNUM]);
+      asm_fprintf (f, "\tldr\t%s, .+20\n", reg_names [IP1_REGNUM]);
+      asm_fprintf (f, "\tldr\t%s, .+24\n", reg_names [STATIC_CHAIN_REGNUM]);
     }
   asm_fprintf (f, "\tbr\t%s\n", reg_names [IP1_REGNUM]);
 
+  /* We always emit a speculation barrier.
+     This is because the same trampoline template is used for every nested
+     function.  Since nested functions are not particularly common or
+     performant we don't worry too much about the extra instructions to copy
+     around.
+     This is not yet a problem, since we have not yet implemented function
+     specific attributes to choose between hardening against straight line
+     speculation or not, but such function specific attributes are likely to
+     happen in the future.  */
+  asm_fprintf (f, "\tdsb\tsy\n\tisb\n");
+
   assemble_aligned_integer (POINTER_BYTES, const0_rtx);
   assemble_aligned_integer (POINTER_BYTES, const0_rtx);
 }
@@ -10861,10 +10872,14 @@ static void
 aarch64_trampoline_init (rtx m_tramp, tree fndecl, rtx chain_value)
 {
   rtx fnaddr, mem, a_tramp;
-  const int tramp_code_sz = 16;
+  const int tramp_code_sz = 24;
 
   /* Don't need to copy the trailing D-words, we fill those in below.  */
-  emit_block_move (m_tramp, assemble_trampoline_template (),
+  /* We create our own memory address in Pmode so that `emit_block_move` can
+     use parts of the backend which expect Pmode addresses.  */
+  rtx temp = convert_memory_address (Pmode, XEXP (m_tramp, 0));
+  emit_block_move (gen_rtx_MEM (BLKmode, temp),
+                  assemble_trampoline_template (),
                   GEN_INT (tramp_code_sz), BLOCK_OP_NORMAL);
   mem = adjust_address (m_tramp, ptr_mode, tramp_code_sz);
   fnaddr = XEXP (DECL_RTL (fndecl), 0);
@@ -11055,6 +11070,8 @@ aarch64_output_casesi (rtx *operands)
   output_asm_insn (buf, operands);
   output_asm_insn (patterns[index][1], operands);
   output_asm_insn ("br\t%3", operands);
+  output_asm_insn (aarch64_sls_barrier (aarch64_harden_sls_retbr_p ()),
+                  operands);
   assemble_label (asm_out_file, label);
   return "";
 }
@@ -22931,6 +22948,22 @@ aarch64_file_end_indicate_exec_stack ()
 #undef GNU_PROPERTY_AARCH64_FEATURE_1_BTI
 #undef GNU_PROPERTY_AARCH64_FEATURE_1_AND
 
+/* Helper function for straight line speculation.
+   Return what barrier should be emitted for straight line speculation
+   mitigation.
+   When not mitigating against straight line speculation this function returns
+   an empty string.
+   When mitigating against straight line speculation, use:
+   * SB when the v8.5-A SB extension is enabled.
+   * DSB+ISB otherwise.  */
+const char *
+aarch64_sls_barrier (int mitigation_required)
+{
+  return mitigation_required
+    ? (TARGET_SB ? "sb" : "dsb\tsy\n\tisb")
+    : "";
+}
+
 /* Target-specific selftests.  */
 
 #if CHECKING_P
index 2be52fd4d73def0007795159298e3d3e8fc4399d..7bb0f147eb81a271319b3134fdd18db7a6fa52bf 100644 (file)
@@ -281,6 +281,7 @@ extern unsigned aarch64_architecture_version;
 #define AARCH64_ISA_F32MM         (aarch64_isa_flags & AARCH64_FL_F32MM)
 #define AARCH64_ISA_F64MM         (aarch64_isa_flags & AARCH64_FL_F64MM)
 #define AARCH64_ISA_BF16          (aarch64_isa_flags & AARCH64_FL_BF16)
+#define AARCH64_ISA_SB            (aarch64_isa_flags & AARCH64_FL_SB)
 
 /* Crypto is an optional extension to AdvSIMD.  */
 #define TARGET_CRYPTO (TARGET_SIMD && AARCH64_ISA_CRYPTO)
@@ -378,6 +379,9 @@ extern unsigned aarch64_architecture_version;
 #define TARGET_FIX_ERR_A53_835769_DEFAULT 1
 #endif
 
+/* SB instruction is enabled through +sb.  */
+#define TARGET_SB (AARCH64_ISA_SB)
+
 /* Apply the workaround for Cortex-A53 erratum 835769.  */
 #define TARGET_FIX_ERR_A53_835769      \
   ((aarch64_fix_a53_err835769 == 2)    \
@@ -1075,8 +1079,10 @@ typedef struct
 
 #define RETURN_ADDR_RTX aarch64_return_addr
 
-/* BTI c + 3 insns + 2 pointer-sized entries.  */
-#define TRAMPOLINE_SIZE        (TARGET_ILP32 ? 24 : 32)
+/* BTI c + 3 insns
+   + sls barrier of DSB + ISB.
+   + 2 pointer-sized entries.  */
+#define TRAMPOLINE_SIZE        (24 + (TARGET_ILP32 ? 8 : 16))
 
 /* Trampolines contain dwords, so must be dword aligned.  */
 #define TRAMPOLINE_ALIGNMENT 64
index c98ec7c35709d5e2c1c9ea8ebbc81f5b6b1a1185..3c363337926ad5fcfd05abf6b7551a991ca9664e 100644 (file)
 ;; Attribute that specifies whether the alternative uses MOVPRFX.
 (define_attr "movprfx" "no,yes" (const_string "no"))
 
+;; Attribute to specify that an alternative has the length of a single
+;; instruction plus a speculation barrier.
+(define_attr "sls_length" "none,retbr,casesi" (const_string "none"))
+
 (define_attr "length" ""
   (cond [(eq_attr "movprfx" "yes")
            (const_int 8)
-        ] (const_int 4)))
+
+        (eq_attr "sls_length" "retbr")
+          (cond [(match_test "!aarch64_harden_sls_retbr_p ()") (const_int 4)
+                 (match_test "TARGET_SB") (const_int 8)]
+                (const_int 12))
+
+        (eq_attr "sls_length" "casesi")
+          (cond [(match_test "!aarch64_harden_sls_retbr_p ()") (const_int 16)
+                 (match_test "TARGET_SB") (const_int 20)]
+                (const_int 24))
+       ]
+         (const_int 4)))
 
 ;; Strictly for compatibility with AArch32 in pipeline models, since AArch64 has
 ;; no predicated insns.
 (define_insn "indirect_jump"
   [(set (pc) (match_operand:DI 0 "register_operand" "r"))]
   ""
-  "br\\t%0"
-  [(set_attr "type" "branch")]
+  {
+    output_asm_insn ("br\\t%0", operands);
+    return aarch64_sls_barrier (aarch64_harden_sls_retbr_p ());
+  }
+  [(set_attr "type" "branch")
+   (set_attr "sls_length" "retbr")]
 )
 
 (define_insn "jump"
   "*
   return aarch64_output_casesi (operands);
   "
-  [(set_attr "length" "16")
+  [(set_attr "sls_length" "casesi")
    (set_attr "type" "branch")]
 )
 
   [(return)]
   ""
   {
+    const char *ret = NULL;
     if (aarch64_return_address_signing_enabled ()
        && TARGET_ARMV8_3
        && !crtl->calls_eh_return)
       {
        if (aarch64_ra_sign_key == AARCH64_KEY_B)
-         return "retab";
+         ret = "retab";
        else
-         return "retaa";
+         ret = "retaa";
       }
-    return "ret";
+    else
+      ret = "ret";
+    output_asm_insn (ret, operands);
+    return aarch64_sls_barrier (aarch64_harden_sls_retbr_p ());
   }
-  [(set_attr "type" "branch")]
+  [(set_attr "type" "branch")
+   (set_attr "sls_length" "retbr")]
 )
 
 (define_expand "return"
 (define_insn "simple_return"
   [(simple_return)]
   ""
-  "ret"
-  [(set_attr "type" "branch")]
+  {
+    output_asm_insn ("ret", operands);
+    return aarch64_sls_barrier (aarch64_harden_sls_retbr_p ());
+  }
+  [(set_attr "type" "branch")
+   (set_attr "sls_length" "retbr")]
 )
 
 (define_insn "*cb<optab><mode>1"
    (unspec:DI [(match_operand:DI 2 "const_int_operand")] UNSPEC_CALLEE_ABI)
    (return)]
   "SIBLING_CALL_P (insn)"
-  "@
-   br\\t%0
-   b\\t%c0"
-  [(set_attr "type" "branch, branch")]
+  {
+    if (which_alternative == 0)
+      {
+       output_asm_insn ("br\\t%0", operands);
+       return aarch64_sls_barrier (aarch64_harden_sls_retbr_p ());
+      }
+    return "b\\t%c0";
+  }
+  [(set_attr "type" "branch, branch")
+   (set_attr "sls_length" "retbr,none")]
 )
 
 (define_insn "*sibcall_value_insn"
    (unspec:DI [(match_operand:DI 3 "const_int_operand")] UNSPEC_CALLEE_ABI)
    (return)]
   "SIBLING_CALL_P (insn)"
-  "@
-   br\\t%1
-   b\\t%c1"
-  [(set_attr "type" "branch, branch")]
+  {
+    if (which_alternative == 0)
+      {
+       output_asm_insn ("br\\t%1", operands);
+       return aarch64_sls_barrier (aarch64_harden_sls_retbr_p ());
+      }
+    return "b\\t%c1";
+  }
+  [(set_attr "type" "branch, branch")
+   (set_attr "sls_length" "retbr,none")]
 )
 
 ;; Call subroutine returning any type.
diff --git a/gcc/testsuite/gcc.target/aarch64/sls-mitigation/sls-miti-retbr-pacret.c b/gcc/testsuite/gcc.target/aarch64/sls-mitigation/sls-miti-retbr-pacret.c
new file mode 100644 (file)
index 0000000..fa1887a
--- /dev/null
@@ -0,0 +1,21 @@
+/* Avoid ILP32 since pacret is only available for LP64 */
+/* { dg-do compile { target { ! ilp32 } } } */
+/* { dg-additional-options "-mharden-sls=retbr -mbranch-protection=pac-ret -march=armv8.3-a" } */
+
+/* Testing the do_return pattern for retaa and retab.  */
+long retbr_subcall(void);
+long retbr_do_return_retaa(void)
+{
+    return retbr_subcall()+1;
+}
+
+__attribute__((target("branch-protection=pac-ret+b-key")))
+long retbr_do_return_retab(void)
+{
+    return retbr_subcall()+1;
+}
+
+/* Ensure there are no BR or RET instructions which are not directly followed
+   by a speculation barrier.  */
+/* { dg-final { scan-assembler-not {\t(br|ret|retaa|retab)\tx[0-9][0-9]?\n\t(?!dsb\tsy\n\tisb)} } } */
+/* { dg-final { scan-assembler-not {ret\t} } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/sls-mitigation/sls-miti-retbr.c b/gcc/testsuite/gcc.target/aarch64/sls-mitigation/sls-miti-retbr.c
new file mode 100644 (file)
index 0000000..76b8d03
--- /dev/null
@@ -0,0 +1,119 @@
+/* We ensure that -Wpedantic is off since it complains about the trampolines
+   we explicitly want to test.  */
+/* { dg-additional-options "-mharden-sls=retbr -Wno-pedantic " } */
+/*
+   Ensure that the SLS hardening of RET and BR leaves no unprotected RET/BR
+   instructions.
+  */
+typedef int (foo) (int, int);
+typedef void (bar) (int, int);
+struct sls_testclass {
+    foo *x;
+    bar *y;
+    int left;
+    int right;
+};
+
+int
+retbr_sibcall_value_insn (struct sls_testclass x)
+{
+  return x.x(x.left, x.right);
+}
+
+void
+retbr_sibcall_insn (struct sls_testclass x)
+{
+  x.y(x.left, x.right);
+}
+
+/* Aim to test two different returns.
+   One that introduces a tail call in the middle of the function, and one that
+   has a normal return.  */
+int
+retbr_multiple_returns (struct sls_testclass x)
+{
+  int temp;
+  if (x.left % 10)
+    return x.x(x.left, 100);
+  else if (x.right % 20)
+    {
+      return x.x(x.left * x.right, 100);
+    }
+  temp = x.left % x.right;
+  temp *= 100;
+  temp /= 2;
+  return temp % 3;
+}
+
+void
+retbr_multiple_returns_void (struct sls_testclass x)
+{
+  if (x.left % 10)
+    {
+      x.y(x.left, 100);
+    }
+  else if (x.right % 20)
+    {
+      x.y(x.left * x.right, 100);
+    }
+  return;
+}
+
+/* Testing the casesi jump via register.  */
+__attribute__ ((optimize ("Os")))
+int
+retbr_casesi_dispatch (struct sls_testclass x)
+{
+  switch (x.left)
+    {
+    case -5:
+      return -2;
+    case -3:
+      return -1;
+    case 0:
+      return 0;
+    case 3:
+      return 1;
+    case 5:
+      break;
+    default:
+      __builtin_unreachable ();
+    }
+  return x.right;
+}
+
+/* Testing the BR in trampolines is mitigated against.  */
+void f1 (void *);
+void f3 (void *, void (*)(void *));
+void f2 (void *);
+
+int
+retbr_trampolines (void *a, int b)
+{
+  if (!b)
+    {
+      f1 (a);
+      return 1;
+    }
+  if (b)
+    {
+      void retbr_tramp_internal (void *c)
+      {
+       if (c == a)
+         f2 (c);
+      }
+      f3 (a, retbr_tramp_internal);
+    }
+  return 0;
+}
+
+/* Testing the indirect_jump pattern.  */
+void
+retbr_indirect_jump (int *buf)
+{
+  __builtin_longjmp(buf, 1);
+}
+
+/* Ensure there are no BR or RET instructions which are not directly followed
+   by a speculation barrier.  */
+/* { dg-final { scan-assembler-not {\t(br|ret|retaa|retab)\tx[0-9][0-9]?\n\t(?!dsb\tsy\n\tisb|sb)} } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/sls-mitigation/sls-mitigation.exp b/gcc/testsuite/gcc.target/aarch64/sls-mitigation/sls-mitigation.exp
new file mode 100644 (file)
index 0000000..8122503
--- /dev/null
@@ -0,0 +1,73 @@
+#  Regression driver for SLS mitigation on AArch64.
+#  Copyright (C) 2020 Free Software Foundation, Inc.
+#  Contributed by ARM Ltd.
+#
+#  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/>.  */
+
+# Exit immediately if this isn't an AArch64 target.
+if {![istarget aarch64*-*-*] } then {
+  return
+}
+
+# Load support procs.
+load_lib gcc-dg.exp
+load_lib torture-options.exp
+
+# If a testcase doesn't have special options, use these.
+global DEFAULT_CFLAGS
+if ![info exists DEFAULT_CFLAGS] then {
+    set DEFAULT_CFLAGS " "
+}
+
+# Initialize `dg'.
+dg-init
+torture-init
+
+# Use different architectures as well as the normal optimisation options.
+# (i.e. use both SB and DSB+ISB barriers).
+
+set save-dg-do-what-default ${dg-do-what-default}
+# Main loop.
+# Run with torture tests (i.e. a bunch of different optimisation levels) just
+# to increase test coverage.
+set dg-do-what-default assemble
+gcc-dg-runtest [lsort [glob -nocomplain $srcdir/$subdir/*.\[cCS\]]] \
+       "-save-temps" $DEFAULT_CFLAGS
+
+# Run the same tests but this time with SB extension.
+# Since not all supported assemblers will support that extension we decide
+# whether to assemble or just compile based on whether the extension is
+# supported for the available assembler.
+
+set templist {}
+foreach x $DG_TORTURE_OPTIONS {
+  lappend templist "$x -march=armv8.3-a+sb "
+  lappend templist "$x -march=armv8-a+sb "
+}
+set-torture-options $templist
+if { [check_effective_target_aarch64_asm_sb_ok] } {
+    set dg-do-what-default assemble
+} else {
+    set dg-do-what-default compile
+}
+gcc-dg-runtest [lsort [glob -nocomplain $srcdir/$subdir/*.\[cCS\]]] \
+       "-save-temps" $DEFAULT_CFLAGS
+set dg-do-what-default ${save-dg-do-what-default}
+
+# All done.
+torture-finish
+dg-finish
index 848cb96aec45769bde9d5b2a77b1f42739208aa0..19aa51d3e3f3ab2c1c120fddbec064ecfb430272 100644 (file)
@@ -9431,7 +9431,7 @@ proc check_effective_target_aarch64_tiny { } {
 # various architecture extensions via the .arch_extension pseudo-op.
 
 foreach { aarch64_ext } { "fp" "simd" "crypto" "crc" "lse" "dotprod" "sve"
-                         "i8mm" "f32mm" "f64mm" "bf16" } {
+                         "i8mm" "f32mm" "f64mm" "bf16" "sb" } {
     eval [string map [list FUNC $aarch64_ext] {
        proc check_effective_target_aarch64_asm_FUNC_ok { } {
          if { [istarget aarch64*-*-*] } {