]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
LoongArch: harden SSP canary set and test routines [PR 125049]
authorXi Ruoyao <xry111@xry111.site>
Tue, 28 Apr 2026 12:32:38 +0000 (20:32 +0800)
committerXi Ruoyao <xry111@xry111.site>
Fri, 8 May 2026 09:17:35 +0000 (17:17 +0800)
Add the stack_protect_combined_{set,test} expanders to expand the
routines as unsplitable insns which does not leave any sensitive data
(the canary value, the canary address, and all the intermediate values
used materializing the address) in a register.  This prevents the
attacker from defeating SSP by probing the canary value from the
register context or overwriting the address spilled onto the stack.

PR target/125049

gcc/

* config/loongarch/predicates.md (ssp_operand): New
define_predicate.
(ssp_normal_operand): New define_predicate.
* config/loongarch/constraints.md (ZE): New define_constraint.
(ZF): New define_constraint.
* config/loongarch/loongarch.md (UNSPEC_SSP): New unspec.
(cbranch4): Add "@" to create gen_cbranch4(machine_mode, ...).
(@stack_protect_combined_set_normal_<mode>): New define_insn.
(@stack_protect_combined_set_extreme_<mode>): New define_insn.
(@stack_protect_combined_test_internal_<mode>): New define_insn.
(stack_protect_combined_set): New define_expand.
(stack_protect_combined_test): New define_expand.
* config/loongarch/loongarch-protos.h
(loongarch_output_asm_load_canary): Declare.
* config/loongarch/loongarch.cc (loongarch_print_operand): Allow
'v' to print d/w for DImode/SImode.
(loongarch_output_asm_load_canary): Implement.

gcc/testsuite/

* gcc.target/loongarch/pr125049.c: New test.

gcc/config/loongarch/constraints.md
gcc/config/loongarch/loongarch-protos.h
gcc/config/loongarch/loongarch.cc
gcc/config/loongarch/loongarch.md
gcc/config/loongarch/predicates.md
gcc/testsuite/gcc.target/loongarch/pr125049.c [new file with mode: 0644]

index 2b10d6851372136c95ed7ad23403954f90894869..b681cf9520588a51274cddcd1534b5d239ad9dd8 100644 (file)
    and offset that is suitable for use in instructions with the same
    addressing mode as @code{preld}."
    (match_test "loongarch_12bit_offset_address_p (op, mode)"))
+
+(define_constraint "ZE"
+  "A symbolic suitable as stack canary in the normal/medium code model."
+  (match_operand 0 "ssp_normal_operand"))
+
+(define_constraint "ZF"
+  "A symbolic suitable as stack canary, but in the extreme code model."
+  (and (match_operand 0 "ssp_operand")
+       (not (match_operand 0 "ssp_normal_operand"))))
index 11575a15454c5c94a13599056045c0143a6b9f6f..e67addf36e3faf74af7a18cf5b3c5a3afecf2e71 100644 (file)
@@ -235,4 +235,5 @@ extern bool loongarch_parse_fmv_features (location_t, string_slice,
 extern void get_feature_mask_for_version (tree, loongarch_fmv_feature_mask *,
                                          auto_vec<unsigned int> *);
 extern int loongarch_compare_version_priority (tree, tree);
+extern void loongarch_output_asm_load_canary (rtx reg, rtx canary, rtx tmp);
 #endif /* ! GCC_LOONGARCH_PROTOS_H */
index 1ecf356ac6cb7d34ff1fc1811f7f74b3bf0aaa69..fd3791e2455310d151faa625b8fb99590b85a1ca 100644 (file)
@@ -6969,12 +6969,14 @@ loongarch_print_operand (FILE *file, rtx op, int letter)
        case E_V4SFmode:
        case E_V8SImode:
        case E_V8SFmode:
+       case E_SImode:
          fprintf (file, "w");
          break;
        case E_V2DImode:
        case E_V2DFmode:
        case E_V4DImode:
        case E_V4DFmode:
+       case E_DImode:
          fprintf (file, "d");
          break;
        default:
@@ -12144,6 +12146,55 @@ loongarch_option_same_function_versions (string_slice str1, const_tree,
   return feature_mask1 == feature_mask2;
 }
 
+/* Output assembly to materialize the address of the stack canary value
+   into reg.  The third argument, tmp, should be and should only be
+   non-NULL if the extreme code model is effective for the canary.  If
+   the fourth arugment, load, is true, the canary value is loaded into
+   the register.
+
+   The assembly cannot be splitted due to security reason.  */
+void
+loongarch_output_asm_load_canary (rtx reg, rtx canary, rtx tmp)
+{
+  gcc_checking_assert (ssp_operand (canary, VOIDmode));
+  gcc_checking_assert ((!tmp) == ssp_normal_operand (canary, VOIDmode));
+  gcc_checking_assert (register_operand (reg, Pmode));
+
+  rtx op[] = {reg, canary, tmp};
+  bool got = (loongarch_classify_symbol (canary) == SYMBOL_GOT_DISP);
+  bool need_ld = false;
+
+  if (la_opt_explicit_relocs != EXPLICIT_RELOCS_ALWAYS)
+    {
+      if (got)
+       output_asm_insn (tmp ? "la.global\t%0,%2,%1" : "la.global\t%0,%1",
+                        op);
+      else
+       output_asm_insn (tmp ? "la.local\t%0,%2,%1" : "la.local\t%0,%1",
+                        op);
+
+      need_ld = true;
+    }
+  else
+    {
+      output_asm_insn ("pcalau12i\t%0,%r1", op);
+      if (!tmp)
+       output_asm_insn ("ld.%v0\t%0,%0,%L1", op);
+      else
+       {
+         output_asm_insn ("addi.d\t%2,$r0,%L1", op);
+         output_asm_insn ("lu32i.d\t%2,%R1", op);
+         output_asm_insn ("lu52i.d\t%2,%2,%H1", op);
+         output_asm_insn ("ldx.d\t%0,%0,%2", op);
+       }
+
+      need_ld = got;
+    }
+
+  if (need_ld)
+    output_asm_insn ("ld.%v0\t%0,%0,0", op);
+}
+
 /* Initialize the GCC target structure.  */
 #undef TARGET_ASM_ALIGNED_HI_OP
 #define TARGET_ASM_ALIGNED_HI_OP "\t.half\t"
index f8693b7f5939f05aba52cf2f3190c4f04d8b23d9..5edba0d511a653c28be1c46fe385ddf3f0d766dd 100644 (file)
@@ -83,6 +83,8 @@
   UNSPEC_LOAD_SYMBOL_OFFSET64
   UNSPEC_LA_PCREL_64_PART1
   UNSPEC_LA_PCREL_64_PART2
+
+  UNSPEC_SSP
 ])
 
 (define_c_enum "unspecv" [
 ;; QImode values so we can force zero-extension.
 (define_mode_iterator BR [(QI "TARGET_64BIT") SI (DI "TARGET_64BIT")])
 
-(define_expand "cbranch<mode>4"
+(define_expand "@cbranch<mode>4"
   [(set (pc)
        (if_then_else (match_operator 0 "comparison_operator"
                        [(match_operand:BR 1 "register_operand")
     operands[0] = loongarch_rewrite_mem_for_simple_ldst (operands[0]);
   })
 
+;; Set and check against stack canary without leaving it in a register.
+;; DO NOT ATTEMPT TO SPLIT THESE INSNS!  It's important for security reason
+;; that the canary value does not live beyond the life of this sequence.
+
+(define_insn "@stack_protect_combined_set_normal_<mode>"
+  [(set (match_operand:P 0 "memory_operand" "=m,ZC")
+        (unspec:P [(mem:P (match_operand:P 1 "ssp_normal_operand"))]
+                 UNSPEC_SSP))
+   (set (match_scratch:P 2 "=&r,&r") (const_int 0))]
+  ""
+{
+  loongarch_output_asm_load_canary (operands[2], operands[1], NULL_RTX);
+  output_asm_insn (which_alternative ? "stptr.d\t%2,%0" : "st.d\t%2,%0",
+                  operands);
+  return "ori\t%2,$r0,0";
+}
+  [(set_attr "type" "store")
+   (set_attr "length" "20")])
+
+(define_insn "@stack_protect_combined_set_extreme_<mode>"
+  [(set (match_operand:P 0 "memory_operand" "=m,ZC")
+        (unspec:P [(mem:P (match_operand:P 1 "ssp_operand"))] UNSPEC_SSP))
+   (set (match_scratch:P 2 "=&r,&r") (const_int 0))
+   (set (match_scratch:P 3 "=&r,&r") (const_int 0))]
+  ""
+{
+  loongarch_output_asm_load_canary (operands[2], operands[1], operands[3]);
+  output_asm_insn (which_alternative ? "stptr.d\t%2,%0" : "st.d\t%2,%0",
+                  operands);
+  return "ori\t%2,$r0,0\n\tori\t%3,$r0,0";
+}
+  [(set_attr "type" "store")
+   (set_attr "length" "36")])
+
+(define_insn "@stack_protect_combined_test_internal_<mode>"
+  [(set (match_operand:P 0 "register_operand" "=r,r,&r,&r")
+       (xor:P
+         (match_operand:P 1 "memory_operand" "=m,ZC,m,ZC")
+           (unspec:P
+             [(mem:P (match_operand:P 2 "ssp_operand" "ZE,ZE,ZF,ZF"))]
+             UNSPEC_SSP)))
+   (set (match_scratch:P 3 "=&r,&r,&r,&r") (const_int 0))]
+  ""
+{
+  rtx t = (which_alternative >= 2 ? operands[0] : NULL_RTX);
+  loongarch_output_asm_load_canary (operands[3], operands[2], t);
+  output_asm_insn ((which_alternative & 1) ? "ldptr.d\t%0,%1"
+                                          : "ld.d\t%0,%1",
+                  operands);
+  return "xor\t%0,%0,%3\n\tori\t%3,$r0,0";
+}
+  [(set_attr "type" "load,load,load,load")
+   (set_attr "length" "24,24,36,36")])
+
+(define_expand "stack_protect_combined_set"
+  [(match_operand 0 "memory_operand")
+   (match_operand 1 "memory_operand")]
+  ""
+{
+  rtx canary = XEXP (operands[1], 0);
+  auto fn = (ssp_normal_operand (canary, VOIDmode)
+            ? gen_stack_protect_combined_set_normal
+            : gen_stack_protect_combined_set_extreme);
+
+  emit_insn (fn (Pmode, operands[0], canary));
+  DONE;
+})
+
+(define_expand "stack_protect_combined_test"
+  [(match_operand 0 "memory_operand")
+   (match_operand 1 "memory_operand")
+   (match_operand 2 "")]
+  ""
+{
+  rtx t = gen_reg_rtx (Pmode);
+  rtx canary = XEXP (operands[1], 0);
+  emit_insn (gen_stack_protect_combined_test_internal (Pmode, t,
+                                                      operands[0],
+                                                      canary));
+  rtx cond = gen_rtx_EQ (VOIDmode, t, const0_rtx);
+  emit_jump_insn (gen_cbranch4 (Pmode, cond, t, const0_rtx, operands[2]));
+  DONE;
+})
+
 ;; Synchronization instructions.
 
 (include "sync.md")
index da46de8ec048c6da7d79f3705b2f5b64e7874c7e..af3b770c4e9da068ec65417f00de82cd8c2e036c 100644 (file)
  (ior (match_operand 0 "register_operand")
       (match_operand 0 "symbolic_off64_operand")))
 
+;; Currently stack canary must be the global symbol __stack_chk_guard.
+(define_predicate "ssp_operand" (match_code "symbol_ref"))
+
+;; If the stack canary is within the normal/medium code model.
+(define_predicate "ssp_normal_operand"
+  (and (match_operand 0 "ssp_operand")
+       (not (match_operand 0 "symbolic_off64_operand"))))
+
 (define_predicate "equality_operator"
   (match_code "eq,ne"))
 
diff --git a/gcc/testsuite/gcc.target/loongarch/pr125049.c b/gcc/testsuite/gcc.target/loongarch/pr125049.c
new file mode 100644 (file)
index 0000000..cfe036e
--- /dev/null
@@ -0,0 +1,50 @@
+/* PR 125049: ensure stack canary and its address are not leaked.  */
+/* { dg-options "-O2 -fstack-protector-strong -ffixed-r30 -ffixed-r31" } */
+/* { dg-do run } */
+/* { dg-require-effective-target fstack_protector } */
+
+extern long __stack_chk_guard;
+register long s7 asm ("s7"), *s8 asm ("s8");
+
+[[gnu::zero_call_used_regs ("all"), gnu::noipa]] void
+init_test (void)
+{
+  s7 = __stack_chk_guard;
+  s8 = &__stack_chk_guard;
+}
+
+[[gnu::always_inline]] static inline void
+check_reg (void)
+{
+#pragma GCC unroll 30
+  for (int i = 4; i < 30; i++)
+    asm goto (
+      "beq $r%0,$s7,%l[error]\n\t"
+      "beq $r%0,$s8,%l[error]\n\t"
+      :
+      : "i" (i)
+      :
+      : error
+    );
+  return;
+error:
+  __builtin_trap ();
+}
+
+[[gnu::noipa]] void
+test (void)
+{
+  char buf[256];
+  asm ("":"+m"(buf));
+
+  check_reg ();
+}
+
+int
+main (void)
+{
+  init_test ();
+  test ();
+
+  check_reg ();
+}