https://gcc.gnu.org/g:690fc9dc78a8f5845cded281ec5aebf19ec94d56

commit r16-8902-g690fc9dc78a8f5845cded281ec5aebf19ec94d56
Author: Xi Ruoyao <[email protected]>
Date:   Tue Apr 28 20:32:38 2026 +0800

    LoongArch: harden SSP canary set and test routines [PR 125049]
    
    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.
    
    (cherry picked from commit 62fdbd084f7ab04cb7a97d6186ffe70acfc70f1b)

Diff:
---
 gcc/config/loongarch/constraints.md           |  9 +++
 gcc/config/loongarch/loongarch-protos.h       |  1 +
 gcc/config/loongarch/loongarch.cc             | 51 ++++++++++++++++
 gcc/config/loongarch/loongarch.md             | 88 ++++++++++++++++++++++++++-
 gcc/config/loongarch/predicates.md            |  8 +++
 gcc/testsuite/gcc.target/loongarch/pr125049.c | 50 +++++++++++++++
 6 files changed, 206 insertions(+), 1 deletion(-)

diff --git a/gcc/config/loongarch/constraints.md 
b/gcc/config/loongarch/constraints.md
index 2b10d6851372..b681cf952058 100644
--- a/gcc/config/loongarch/constraints.md
+++ b/gcc/config/loongarch/constraints.md
@@ -370,3 +370,12 @@
    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"))))
diff --git a/gcc/config/loongarch/loongarch-protos.h 
b/gcc/config/loongarch/loongarch-protos.h
index 11575a15454c..e67addf36e3f 100644
--- a/gcc/config/loongarch/loongarch-protos.h
+++ b/gcc/config/loongarch/loongarch-protos.h
@@ -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 */
diff --git a/gcc/config/loongarch/loongarch.cc 
b/gcc/config/loongarch/loongarch.cc
index 23d000bc0b34..d97569c83116 100644
--- a/gcc/config/loongarch/loongarch.cc
+++ b/gcc/config/loongarch/loongarch.cc
@@ -6970,12 +6970,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:
@@ -12145,6 +12147,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"
diff --git a/gcc/config/loongarch/loongarch.md 
b/gcc/config/loongarch/loongarch.md
index 77b3298078af..a7a22f58410f 100644
--- a/gcc/config/loongarch/loongarch.md
+++ b/gcc/config/loongarch/loongarch.md
@@ -83,6 +83,8 @@
   UNSPEC_LOAD_SYMBOL_OFFSET64
   UNSPEC_LA_PCREL_64_PART1
   UNSPEC_LA_PCREL_64_PART2
+
+  UNSPEC_SSP
 ])
 
 (define_c_enum "unspecv" [
@@ -3677,7 +3679,7 @@
 ;; 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")
@@ -4986,6 +4988,90 @@
     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")
diff --git a/gcc/config/loongarch/predicates.md 
b/gcc/config/loongarch/predicates.md
index da46de8ec048..af3b770c4e9d 100644
--- a/gcc/config/loongarch/predicates.md
+++ b/gcc/config/loongarch/predicates.md
@@ -610,6 +610,14 @@
  (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
index 000000000000..cfe036e20615
--- /dev/null
+++ b/gcc/testsuite/gcc.target/loongarch/pr125049.c
@@ -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 ();
+}

Reply via email to