https://gcc.gnu.org/g:4d7d4d282de299ab3f09134b880f9595f60db30a

commit r13-10356-g4d7d4d282de299ab3f09134b880f9595f60db30a
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_symbol_extreme_p): Declare.
            (loongarch_output_asm_load_canary): Declare.
            * config/loongarch/loongarch.cc (loongarch_print_operand): Allow
            'v' to print d/w for DImode/SImode.
            (loongarch_symbol_extreme_p): Remove static.
            (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       |  2 +
 gcc/config/loongarch/loongarch.cc             | 58 +++++++++++++++++-
 gcc/config/loongarch/loongarch.md             | 88 ++++++++++++++++++++++++++-
 gcc/config/loongarch/predicates.md            | 16 +++++
 gcc/testsuite/gcc.target/loongarch/pr125049.c | 50 +++++++++++++++
 6 files changed, 221 insertions(+), 2 deletions(-)

diff --git a/gcc/config/loongarch/constraints.md 
b/gcc/config/loongarch/constraints.md
index cb7fa688cebe..594c7b97b698 100644
--- a/gcc/config/loongarch/constraints.md
+++ b/gcc/config/loongarch/constraints.md
@@ -200,3 +200,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 0f608ee51790..103909601d17 100644
--- a/gcc/config/loongarch/loongarch-protos.h
+++ b/gcc/config/loongarch/loongarch-protos.h
@@ -180,4 +180,6 @@ extern rtx loongarch_expand_builtin (tree, rtx, rtx 
subtarget ATTRIBUTE_UNUSED,
                                     machine_mode, int);
 extern tree loongarch_build_builtin_va_list (void);
 
+extern bool loongarch_symbol_extreme_p (enum loongarch_symbol_type);
+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 be26e156beb8..6739c53c57fe 100644
--- a/gcc/config/loongarch/loongarch.cc
+++ b/gcc/config/loongarch/loongarch.cc
@@ -2825,7 +2825,7 @@ loongarch_force_address (rtx x, machine_mode mode)
   return x;
 }
 
-static bool
+bool
 loongarch_symbol_extreme_p (enum loongarch_symbol_type type)
 {
   switch (type)
@@ -5153,6 +5153,20 @@ loongarch_print_operand (FILE *file, rtx op, int letter)
       fputc (',', file);
       break;
 
+    case 'v':
+      switch (GET_MODE (op))
+       {
+       case E_SImode:
+         fprintf (file, "w");
+         break;
+       case E_DImode:
+         fprintf (file, "d");
+         break;
+       default:
+         output_operand_lossage ("invalid use of '%%%c'", letter);
+       }
+      break;
+
     default:
       switch (code)
        {
@@ -6631,6 +6645,48 @@ loongarch_asan_shadow_offset (void)
   return TARGET_64BIT ? (HOST_WIDE_INT_1 << 46) : 0;
 }
 
+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 (!TARGET_EXPLICIT_RELOCS)
+    {
+      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 acf1269b4020..dcb8f9e10fee 100644
--- a/gcc/config/loongarch/loongarch.md
+++ b/gcc/config/loongarch/loongarch.md
@@ -74,6 +74,8 @@
 
   UNSPEC_SIBCALL_VALUE_MULTIPLE_INTERNAL_1
   UNSPEC_CALL_VALUE_MULTIPLE_INTERNAL_1
+
+  UNSPEC_SSP
 ])
 
 (define_c_enum "unspecv" [
@@ -2698,7 +2700,7 @@
   [(set_attr "type" "branch")])
 
 
-(define_expand "cbranch<mode>4"
+(define_expand "@cbranch<mode>4"
   [(set (pc)
        (if_then_else (match_operator 0 "comparison_operator"
                        [(match_operand:GPR 1 "register_operand")
@@ -3584,6 +3586,90 @@
   [(set_attr "type" "unknown")
    (set_attr "mode" "<MODE>")])
 
+;; 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 95140280f1e6..e72d100b67b7 100644
--- a/gcc/config/loongarch/predicates.md
+++ b/gcc/config/loongarch/predicates.md
@@ -248,6 +248,22 @@
   return loongarch_symbolic_constant_p (op, &type);
 })
 
+(define_predicate "symbolic_off64_operand"
+ (match_code "const,symbol_ref,label_ref")
+{
+  enum loongarch_symbol_type type;
+  return loongarch_symbolic_constant_p (op, &type)
+        && loongarch_symbol_extreme_p (type);
+})
+
+;; 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